db_open.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  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. "fmt"
  9. "log/slog"
  10. "os"
  11. "path/filepath"
  12. "sync"
  13. "time"
  14. "github.com/syncthing/syncthing/internal/db"
  15. "github.com/syncthing/syncthing/internal/slogutil"
  16. "github.com/syncthing/syncthing/lib/build"
  17. )
  18. const (
  19. maxDBConns = 16
  20. minDeleteRetention = 24 * time.Hour
  21. )
  22. type DB struct {
  23. *baseDB
  24. pathBase string
  25. deleteRetention time.Duration
  26. folderDBsMut sync.RWMutex
  27. folderDBs map[string]*folderDB
  28. folderDBOpener func(folder, path string, deleteRetention time.Duration) (*folderDB, error)
  29. }
  30. var _ db.DB = (*DB)(nil)
  31. type Option func(*DB)
  32. func WithDeleteRetention(d time.Duration) Option {
  33. return func(s *DB) {
  34. if d <= 0 {
  35. s.deleteRetention = 0
  36. } else {
  37. s.deleteRetention = max(d, minDeleteRetention)
  38. }
  39. }
  40. }
  41. func Open(path string, opts ...Option) (*DB, error) {
  42. pragmas := []string{
  43. "journal_mode = WAL",
  44. "optimize = 0x10002",
  45. "auto_vacuum = INCREMENTAL",
  46. fmt.Sprintf("application_id = %d", applicationIDMain),
  47. }
  48. schemas := []string{
  49. "sql/schema/common/*",
  50. "sql/schema/main/*",
  51. }
  52. migrations := []string{
  53. "sql/migrations/common/*",
  54. "sql/migrations/main/*",
  55. }
  56. _ = os.MkdirAll(path, 0o700)
  57. initTmpDir(path)
  58. mainPath := filepath.Join(path, "main.db")
  59. mainBase, err := openBase(mainPath, maxDBConns, pragmas, schemas, migrations)
  60. if err != nil {
  61. return nil, err
  62. }
  63. db := &DB{
  64. pathBase: path,
  65. baseDB: mainBase,
  66. folderDBs: make(map[string]*folderDB),
  67. folderDBOpener: openFolderDB,
  68. }
  69. for _, opt := range opts {
  70. opt(db)
  71. }
  72. if err := db.cleanDroppedFolders(); err != nil {
  73. slog.Warn("Failed to clean dropped folders", slogutil.Error(err))
  74. }
  75. if err := db.startFolderDatabases(); err != nil {
  76. return nil, wrap(err)
  77. }
  78. return db, nil
  79. }
  80. // Open the database with options suitable for the migration inserts. This
  81. // is not a safe mode of operation for normal processing, use only for bulk
  82. // inserts with a close afterwards.
  83. func OpenForMigration(path string) (*DB, error) {
  84. pragmas := []string{
  85. "journal_mode = OFF",
  86. "foreign_keys = 0",
  87. "synchronous = 0",
  88. "locking_mode = EXCLUSIVE",
  89. fmt.Sprintf("application_id = %d", applicationIDMain),
  90. }
  91. schemas := []string{
  92. "sql/schema/common/*",
  93. "sql/schema/main/*",
  94. }
  95. migrations := []string{
  96. "sql/migrations/common/*",
  97. "sql/migrations/main/*",
  98. }
  99. _ = os.MkdirAll(path, 0o700)
  100. initTmpDir(path)
  101. mainPath := filepath.Join(path, "main.db")
  102. mainBase, err := openBase(mainPath, 1, pragmas, schemas, migrations)
  103. if err != nil {
  104. return nil, err
  105. }
  106. db := &DB{
  107. pathBase: path,
  108. baseDB: mainBase,
  109. folderDBs: make(map[string]*folderDB),
  110. folderDBOpener: openFolderDBForMigration,
  111. }
  112. if err := db.cleanDroppedFolders(); err != nil {
  113. slog.Warn("Failed to clean dropped folders", slogutil.Error(err))
  114. }
  115. return db, nil
  116. }
  117. func (s *DB) Close() error {
  118. s.folderDBsMut.Lock()
  119. defer s.folderDBsMut.Unlock()
  120. for folder, fdb := range s.folderDBs {
  121. fdb.Close()
  122. delete(s.folderDBs, folder)
  123. }
  124. return wrap(s.baseDB.Close())
  125. }
  126. func initTmpDir(path string) {
  127. if build.IsWindows || build.IsDarwin || os.Getenv("SQLITE_TMPDIR") != "" {
  128. // Doesn't use SQLITE_TMPDIR, isn't likely to have a tiny
  129. // ram-backed temp directory, or already set to something.
  130. return
  131. }
  132. // Attempt to override the SQLite temporary directory by setting the
  133. // env var prior to the (first) database being opened and hence
  134. // SQLite becoming initialized. We set the temp dir to the same
  135. // place we store the database, in the hope that there will be
  136. // enough space there for the operations it needs to perform, as
  137. // opposed to /tmp and similar, on some systems.
  138. dbTmpDir := filepath.Join(path, ".tmp")
  139. if err := os.MkdirAll(dbTmpDir, 0o700); err == nil {
  140. os.Setenv("SQLITE_TMPDIR", dbTmpDir)
  141. } else {
  142. slog.Warn("Failed to create temp directory for SQLite", slogutil.FilePath(dbTmpDir), slogutil.Error(err))
  143. }
  144. }