db_open.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. "log/slog"
  9. "os"
  10. "path/filepath"
  11. "sync"
  12. "time"
  13. "github.com/syncthing/syncthing/internal/db"
  14. "github.com/syncthing/syncthing/internal/slogutil"
  15. )
  16. const (
  17. maxDBConns = 16
  18. minDeleteRetention = 24 * time.Hour
  19. )
  20. type DB struct {
  21. *baseDB
  22. pathBase string
  23. deleteRetention time.Duration
  24. folderDBsMut sync.RWMutex
  25. folderDBs map[string]*folderDB
  26. folderDBOpener func(folder, path string, deleteRetention time.Duration) (*folderDB, error)
  27. }
  28. var _ db.DB = (*DB)(nil)
  29. type Option func(*DB)
  30. func WithDeleteRetention(d time.Duration) Option {
  31. return func(s *DB) {
  32. if d <= 0 {
  33. s.deleteRetention = 0
  34. } else {
  35. s.deleteRetention = max(d, minDeleteRetention)
  36. }
  37. }
  38. }
  39. func Open(path string, opts ...Option) (*DB, error) {
  40. pragmas := []string{
  41. "journal_mode = WAL",
  42. "optimize = 0x10002",
  43. "auto_vacuum = INCREMENTAL",
  44. "default_temp_store = MEMORY",
  45. "temp_store = MEMORY",
  46. }
  47. schemas := []string{
  48. "sql/schema/common/*",
  49. "sql/schema/main/*",
  50. }
  51. migrations := []string{
  52. "sql/migrations/common/*",
  53. "sql/migrations/main/*",
  54. }
  55. _ = os.MkdirAll(path, 0o700)
  56. mainPath := filepath.Join(path, "main.db")
  57. mainBase, err := openBase(mainPath, maxDBConns, pragmas, schemas, migrations)
  58. if err != nil {
  59. return nil, err
  60. }
  61. db := &DB{
  62. pathBase: path,
  63. baseDB: mainBase,
  64. folderDBs: make(map[string]*folderDB),
  65. folderDBOpener: openFolderDB,
  66. }
  67. for _, opt := range opts {
  68. opt(db)
  69. }
  70. if err := db.cleanDroppedFolders(); err != nil {
  71. slog.Warn("Failed to clean dropped folders", slogutil.Error(err))
  72. }
  73. return db, nil
  74. }
  75. // Open the database with options suitable for the migration inserts. This
  76. // is not a safe mode of operation for normal processing, use only for bulk
  77. // inserts with a close afterwards.
  78. func OpenForMigration(path string) (*DB, error) {
  79. pragmas := []string{
  80. "journal_mode = OFF",
  81. "default_temp_store = MEMORY",
  82. "temp_store = MEMORY",
  83. "foreign_keys = 0",
  84. "synchronous = 0",
  85. "locking_mode = EXCLUSIVE",
  86. }
  87. schemas := []string{
  88. "sql/schema/common/*",
  89. "sql/schema/main/*",
  90. }
  91. migrations := []string{
  92. "sql/migrations/common/*",
  93. "sql/migrations/main/*",
  94. }
  95. _ = os.MkdirAll(path, 0o700)
  96. mainPath := filepath.Join(path, "main.db")
  97. mainBase, err := openBase(mainPath, 1, pragmas, schemas, migrations)
  98. if err != nil {
  99. return nil, err
  100. }
  101. db := &DB{
  102. pathBase: path,
  103. baseDB: mainBase,
  104. folderDBs: make(map[string]*folderDB),
  105. folderDBOpener: openFolderDBForMigration,
  106. }
  107. if err := db.cleanDroppedFolders(); err != nil {
  108. slog.Warn("Failed to clean dropped folders", slogutil.Error(err))
  109. }
  110. return db, nil
  111. }
  112. func OpenTemp() (*DB, error) {
  113. // SQLite has a memory mode, but it works differently with concurrency
  114. // compared to what we need with the WAL mode. So, no memory databases
  115. // for now.
  116. dir, err := os.MkdirTemp("", "syncthing-db")
  117. if err != nil {
  118. return nil, wrap(err)
  119. }
  120. path := filepath.Join(dir, "db")
  121. slog.Debug("Test DB", slogutil.FilePath(path))
  122. return Open(path)
  123. }
  124. func (s *DB) Close() error {
  125. s.folderDBsMut.Lock()
  126. defer s.folderDBsMut.Unlock()
  127. for folder, fdb := range s.folderDBs {
  128. fdb.Close()
  129. delete(s.folderDBs, folder)
  130. }
  131. return wrap(s.baseDB.Close())
  132. }