db_local.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. // Copyright (C) 2025 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package sqlite
  7. import (
  8. "database/sql"
  9. "errors"
  10. "fmt"
  11. "iter"
  12. "github.com/syncthing/syncthing/internal/db"
  13. "github.com/syncthing/syncthing/internal/itererr"
  14. "github.com/syncthing/syncthing/lib/osutil"
  15. "github.com/syncthing/syncthing/lib/protocol"
  16. )
  17. func (s *DB) GetDeviceFile(folder string, device protocol.DeviceID, file string) (protocol.FileInfo, bool, error) {
  18. file = osutil.NormalizedFilename(file)
  19. var ind indirectFI
  20. err := s.stmt(`
  21. SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
  22. INNER JOIN files f on fi.sequence = f.sequence
  23. LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
  24. INNER JOIN devices d ON f.device_idx = d.idx
  25. INNER JOIN folders o ON f.folder_idx = o.idx
  26. WHERE o.folder_id = ? AND d.device_id = ? AND f.name = ?
  27. `).Get(&ind, folder, device.String(), file)
  28. if errors.Is(err, sql.ErrNoRows) {
  29. return protocol.FileInfo{}, false, nil
  30. }
  31. if err != nil {
  32. return protocol.FileInfo{}, false, wrap(err)
  33. }
  34. fi, err := ind.FileInfo()
  35. if err != nil {
  36. return protocol.FileInfo{}, false, wrap(err, "indirect")
  37. }
  38. return fi, true, nil
  39. }
  40. func (s *DB) AllLocalFiles(folder string, device protocol.DeviceID) (iter.Seq[protocol.FileInfo], func() error) {
  41. it, errFn := iterStructs[indirectFI](s.stmt(`
  42. SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
  43. INNER JOIN files f on fi.sequence = f.sequence
  44. LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
  45. INNER JOIN folders o ON o.idx = f.folder_idx
  46. INNER JOIN devices d ON d.idx = f.device_idx
  47. WHERE o.folder_id = ? AND d.device_id = ?
  48. `).Queryx(folder, device.String()))
  49. return itererr.Map(it, errFn, indirectFI.FileInfo)
  50. }
  51. func (s *DB) AllLocalFilesBySequence(folder string, device protocol.DeviceID, startSeq int64, limit int) (iter.Seq[protocol.FileInfo], func() error) {
  52. var limitStr string
  53. if limit > 0 {
  54. limitStr = fmt.Sprintf(" LIMIT %d", limit)
  55. }
  56. it, errFn := iterStructs[indirectFI](s.stmt(`
  57. SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
  58. INNER JOIN files f on fi.sequence = f.sequence
  59. LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
  60. INNER JOIN folders o ON o.idx = f.folder_idx
  61. INNER JOIN devices d ON d.idx = f.device_idx
  62. WHERE o.folder_id = ? AND d.device_id = ? AND f.sequence >= ?
  63. ORDER BY f.sequence`+limitStr).Queryx(
  64. folder, device.String(), startSeq))
  65. return itererr.Map(it, errFn, indirectFI.FileInfo)
  66. }
  67. func (s *DB) AllLocalFilesWithPrefix(folder string, device protocol.DeviceID, prefix string) (iter.Seq[protocol.FileInfo], func() error) {
  68. if prefix == "" {
  69. return s.AllLocalFiles(folder, device)
  70. }
  71. prefix = osutil.NormalizedFilename(prefix)
  72. end := prefixEnd(prefix)
  73. it, errFn := iterStructs[indirectFI](s.sql.Queryx(`
  74. SELECT fi.fiprotobuf, bl.blprotobuf FROM fileinfos fi
  75. INNER JOIN files f on fi.sequence = f.sequence
  76. LEFT JOIN blocklists bl ON bl.blocklist_hash = f.blocklist_hash
  77. INNER JOIN folders o ON o.idx = f.folder_idx
  78. INNER JOIN devices d ON d.idx = f.device_idx
  79. WHERE o.folder_id = ? AND d.device_id = ? AND f.name >= ? AND f.name < ?
  80. `, folder, device.String(), prefix, end))
  81. return itererr.Map(it, errFn, indirectFI.FileInfo)
  82. }
  83. func (s *DB) AllLocalFilesWithBlocksHash(folder string, h []byte) (iter.Seq[db.FileMetadata], func() error) {
  84. return iterStructs[db.FileMetadata](s.stmt(`
  85. SELECT f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f
  86. INNER JOIN folders o ON o.idx = f.folder_idx
  87. WHERE o.folder_id = ? AND f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ?
  88. `).Queryx(folder, h))
  89. }
  90. func (s *DB) AllLocalFilesWithBlocksHashAnyFolder(h []byte) (iter.Seq2[string, db.FileMetadata], func() error) {
  91. type row struct {
  92. FolderID string `db:"folder_id"`
  93. db.FileMetadata
  94. }
  95. it, errFn := iterStructs[row](s.stmt(`
  96. SELECT o.folder_id, f.sequence, f.name, f.type, f.modified as modnanos, f.size, f.deleted, f.invalid, f.local_flags as localflags FROM files f
  97. INNER JOIN folders o ON o.idx = f.folder_idx
  98. WHERE f.device_idx = {{.LocalDeviceIdx}} AND f.blocklist_hash = ?
  99. `).Queryx(h))
  100. return itererr.Map2(it, errFn, func(r row) (string, db.FileMetadata, error) {
  101. return r.FolderID, r.FileMetadata, nil
  102. })
  103. }
  104. func (s *DB) AllLocalBlocksWithHash(hash []byte) (iter.Seq[db.BlockMapEntry], func() error) {
  105. // We involve the files table in this select because deletion of blocks
  106. // & blocklists is deferred (garbage collected) while the files list is
  107. // not. This filters out blocks that are in fact deleted.
  108. return iterStructs[db.BlockMapEntry](s.stmt(`
  109. SELECT f.blocklist_hash as blocklisthash, b.idx as blockindex, b.offset, b.size FROM files f
  110. LEFT JOIN blocks b ON f.blocklist_hash = b.blocklist_hash
  111. WHERE f.device_idx = {{.LocalDeviceIdx}} AND b.hash = ?
  112. `).Queryx(hash))
  113. }