db_global_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  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. "slices"
  9. "testing"
  10. "github.com/syncthing/syncthing/lib/config"
  11. "github.com/syncthing/syncthing/lib/protocol"
  12. )
  13. func TestNeed(t *testing.T) {
  14. t.Helper()
  15. db, err := Open(t.TempDir())
  16. if err != nil {
  17. t.Fatal(err)
  18. }
  19. t.Cleanup(func() {
  20. if err := db.Close(); err != nil {
  21. t.Fatal(err)
  22. }
  23. })
  24. // Some local files
  25. var v protocol.Vector
  26. baseV := v.Update(1)
  27. newerV := baseV.Update(42)
  28. files := []protocol.FileInfo{
  29. genFile("test1", 1, 0), // remote need
  30. genFile("test2", 2, 0), // local need
  31. genFile("test3", 3, 0), // global
  32. }
  33. files[0].Version = baseV
  34. files[1].Version = baseV
  35. files[2].Version = newerV
  36. err = db.Update(folderID, protocol.LocalDeviceID, files)
  37. if err != nil {
  38. t.Fatal(err)
  39. }
  40. // Some remote files
  41. remote := []protocol.FileInfo{
  42. genFile("test2", 2, 100), // global
  43. genFile("test3", 3, 101), // remote need
  44. genFile("test4", 4, 102), // local need
  45. }
  46. remote[0].Version = newerV
  47. remote[1].Version = baseV
  48. remote[2].Version = newerV
  49. err = db.Update(folderID, protocol.DeviceID{42}, remote)
  50. if err != nil {
  51. t.Fatal(err)
  52. }
  53. // A couple are needed locally
  54. localNeed := fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0)))
  55. if !slices.Equal(localNeed, []string{"test2", "test4"}) {
  56. t.Log(localNeed)
  57. t.Fatal("bad local need")
  58. }
  59. // Another couple are needed remotely
  60. remoteNeed := fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0)))
  61. if !slices.Equal(remoteNeed, []string{"test1", "test3"}) {
  62. t.Log(remoteNeed)
  63. t.Fatal("bad remote need")
  64. }
  65. }
  66. func TestDropRecalcsGlobal(t *testing.T) {
  67. // When we drop a device we may get a new global
  68. t.Parallel()
  69. t.Run("DropAllFiles", func(t *testing.T) {
  70. t.Parallel()
  71. testDropWithDropper(t, func(t *testing.T, db *DB) {
  72. t.Helper()
  73. if err := db.DropAllFiles(folderID, protocol.DeviceID{42}); err != nil {
  74. t.Fatal(err)
  75. }
  76. })
  77. })
  78. t.Run("DropDevice", func(t *testing.T) {
  79. t.Parallel()
  80. testDropWithDropper(t, func(t *testing.T, db *DB) {
  81. t.Helper()
  82. if err := db.DropDevice(protocol.DeviceID{42}); err != nil {
  83. t.Fatal(err)
  84. }
  85. })
  86. })
  87. t.Run("DropFilesNamed", func(t *testing.T) {
  88. t.Parallel()
  89. testDropWithDropper(t, func(t *testing.T, db *DB) {
  90. t.Helper()
  91. if err := db.DropFilesNamed(folderID, protocol.DeviceID{42}, []string{"test1", "test42"}); err != nil {
  92. t.Fatal(err)
  93. }
  94. })
  95. })
  96. }
  97. func testDropWithDropper(t *testing.T, dropper func(t *testing.T, db *DB)) {
  98. t.Helper()
  99. db, err := Open(t.TempDir())
  100. if err != nil {
  101. t.Fatal(err)
  102. }
  103. t.Cleanup(func() {
  104. if err := db.Close(); err != nil {
  105. t.Fatal(err)
  106. }
  107. })
  108. // Some local files
  109. err = db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{
  110. genFile("test1", 1, 0),
  111. genFile("test2", 2, 0),
  112. })
  113. if err != nil {
  114. t.Fatal(err)
  115. }
  116. // Some remote files
  117. remote := []protocol.FileInfo{
  118. genFile("test1", 3, 0),
  119. }
  120. remote[0].Version = remote[0].Version.Update(42)
  121. err = db.Update(folderID, protocol.DeviceID{42}, remote)
  122. if err != nil {
  123. t.Fatal(err)
  124. }
  125. // Remote test1 wins as the global, verify.
  126. count, err := db.CountGlobal(folderID)
  127. if err != nil {
  128. t.Fatal(err)
  129. }
  130. if count.Bytes != (2+3)*128<<10 {
  131. t.Log(count)
  132. t.Fatal("bad global size to begin with")
  133. }
  134. if g, ok, err := db.GetGlobalFile(folderID, "test1"); err != nil || !ok {
  135. t.Fatal("missing global to begin with")
  136. } else if g.Size != 3*128<<10 {
  137. t.Fatal("remote test1 should be the global")
  138. }
  139. // Now remove that remote device
  140. dropper(t, db)
  141. // Our test1 should now be the global
  142. count, err = db.CountGlobal(folderID)
  143. if err != nil {
  144. t.Fatal(err)
  145. }
  146. if count.Bytes != (1+2)*128<<10 {
  147. t.Log(count)
  148. t.Fatal("bad global size after drop")
  149. }
  150. if g, ok, err := db.GetGlobalFile(folderID, "test1"); err != nil || !ok {
  151. t.Fatal("missing global after drop")
  152. } else if g.Size != 1*128<<10 {
  153. t.Fatal("local test1 should be the global")
  154. }
  155. }
  156. func TestNeedDeleted(t *testing.T) {
  157. t.Parallel()
  158. db, err := Open(t.TempDir())
  159. if err != nil {
  160. t.Fatal(err)
  161. }
  162. t.Cleanup(func() {
  163. if err := db.Close(); err != nil {
  164. t.Fatal(err)
  165. }
  166. })
  167. // Some local files
  168. err = db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{
  169. genFile("test1", 1, 0),
  170. genFile("test2", 2, 0),
  171. })
  172. if err != nil {
  173. t.Fatal(err)
  174. }
  175. // A remote deleted file
  176. remote := []protocol.FileInfo{
  177. genFile("test1", 1, 101),
  178. }
  179. remote[0].SetDeleted(42)
  180. err = db.Update(folderID, protocol.DeviceID{42}, remote)
  181. if err != nil {
  182. t.Fatal(err)
  183. }
  184. // We need the one deleted file
  185. s, err := db.CountNeed(folderID, protocol.LocalDeviceID)
  186. if err != nil {
  187. t.Fatal(err)
  188. }
  189. if s.Bytes != 0 || s.Deleted != 1 {
  190. t.Log(s)
  191. t.Error("bad need")
  192. }
  193. }
  194. func TestDontNeedIgnored(t *testing.T) {
  195. t.Parallel()
  196. db, err := Open(t.TempDir())
  197. if err != nil {
  198. t.Fatal(err)
  199. }
  200. t.Cleanup(func() {
  201. if err := db.Close(); err != nil {
  202. t.Fatal(err)
  203. }
  204. })
  205. // A remote file
  206. files := []protocol.FileInfo{
  207. genFile("test1", 1, 103),
  208. }
  209. err = db.Update(folderID, protocol.DeviceID{42}, files)
  210. if err != nil {
  211. t.Fatal(err)
  212. }
  213. // Which we've ignored locally
  214. files[0].SetIgnored()
  215. err = db.Update(folderID, protocol.LocalDeviceID, files)
  216. if err != nil {
  217. t.Fatal(err)
  218. }
  219. // We don't need it
  220. s, err := db.CountNeed(folderID, protocol.LocalDeviceID)
  221. if err != nil {
  222. t.Fatal(err)
  223. }
  224. if s.Bytes != 0 || s.Files != 0 {
  225. t.Log(s)
  226. t.Error("bad need")
  227. }
  228. // It shouldn't show up in the need list
  229. names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))
  230. if len(names) != 0 {
  231. t.Log(names)
  232. t.Error("need no files")
  233. }
  234. }
  235. func TestDontNeedRemoteInvalid(t *testing.T) {
  236. t.Parallel()
  237. db, err := Open(t.TempDir())
  238. if err != nil {
  239. t.Fatal(err)
  240. }
  241. t.Cleanup(func() {
  242. if err := db.Close(); err != nil {
  243. t.Fatal(err)
  244. }
  245. })
  246. // A remote file with the invalid bit set
  247. files := []protocol.FileInfo{
  248. genFile("test1", 1, 103),
  249. }
  250. files[0].LocalFlags = protocol.FlagLocalRemoteInvalid
  251. err = db.Update(folderID, protocol.DeviceID{42}, files)
  252. if err != nil {
  253. t.Fatal(err)
  254. }
  255. // It's not part of the global size
  256. s, err := db.CountGlobal(folderID)
  257. if err != nil {
  258. t.Fatal(err)
  259. }
  260. if s.Bytes != 0 || s.Files != 0 {
  261. t.Log(s)
  262. t.Error("bad global")
  263. }
  264. // We don't need it
  265. s, err = db.CountNeed(folderID, protocol.LocalDeviceID)
  266. if err != nil {
  267. t.Fatal(err)
  268. }
  269. if s.Bytes != 0 || s.Files != 0 {
  270. t.Log(s)
  271. t.Error("bad need")
  272. }
  273. // It shouldn't show up in the need list
  274. names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))
  275. if len(names) != 0 {
  276. t.Log(names)
  277. t.Error("need no files")
  278. }
  279. }
  280. func TestRemoteDontNeedLocalIgnored(t *testing.T) {
  281. t.Parallel()
  282. db, err := Open(t.TempDir())
  283. if err != nil {
  284. t.Fatal(err)
  285. }
  286. t.Cleanup(func() {
  287. if err := db.Close(); err != nil {
  288. t.Fatal(err)
  289. }
  290. })
  291. // A local ignored file
  292. file := genFile("test1", 1, 103)
  293. file.SetIgnored()
  294. files := []protocol.FileInfo{file}
  295. err = db.Update(folderID, protocol.LocalDeviceID, files)
  296. if err != nil {
  297. t.Fatal(err)
  298. }
  299. // Which the remote doesn't have (no update)
  300. // They don't need it
  301. s, err := db.CountNeed(folderID, protocol.DeviceID{42})
  302. if err != nil {
  303. t.Fatal(err)
  304. }
  305. if s.Bytes != 0 || s.Files != 0 {
  306. t.Log(s)
  307. t.Error("bad need")
  308. }
  309. // It shouldn't show up in their need list
  310. names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0))
  311. if len(names) != 0 {
  312. t.Log(names)
  313. t.Error("need no files")
  314. }
  315. }
  316. func TestLocalDontNeedDeletedMissing(t *testing.T) {
  317. t.Parallel()
  318. db, err := Open(t.TempDir())
  319. if err != nil {
  320. t.Fatal(err)
  321. }
  322. t.Cleanup(func() {
  323. if err := db.Close(); err != nil {
  324. t.Fatal(err)
  325. }
  326. })
  327. // A remote deleted file
  328. file := genFile("test1", 1, 103)
  329. file.SetDeleted(42)
  330. files := []protocol.FileInfo{file}
  331. err = db.Update(folderID, protocol.DeviceID{42}, files)
  332. if err != nil {
  333. t.Fatal(err)
  334. }
  335. // Which we don't have (no local update)
  336. // We don't need it
  337. s, err := db.CountNeed(folderID, protocol.LocalDeviceID)
  338. if err != nil {
  339. t.Fatal(err)
  340. }
  341. if s.Bytes != 0 || s.Files != 0 || s.Deleted != 0 {
  342. t.Log(s)
  343. t.Error("bad need")
  344. }
  345. // It shouldn't show up in the need list
  346. names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))
  347. if len(names) != 0 {
  348. t.Log(names)
  349. t.Error("need no files")
  350. }
  351. }
  352. func TestRemoteDontNeedDeletedMissing(t *testing.T) {
  353. t.Parallel()
  354. db, err := Open(t.TempDir())
  355. if err != nil {
  356. t.Fatal(err)
  357. }
  358. t.Cleanup(func() {
  359. if err := db.Close(); err != nil {
  360. t.Fatal(err)
  361. }
  362. })
  363. // A local deleted file
  364. file := genFile("test1", 1, 103)
  365. file.SetDeleted(42)
  366. files := []protocol.FileInfo{file}
  367. err = db.Update(folderID, protocol.LocalDeviceID, files)
  368. if err != nil {
  369. t.Fatal(err)
  370. }
  371. // Which the remote doesn't have (no local update)
  372. // They don't need it
  373. s, err := db.CountNeed(folderID, protocol.DeviceID{42})
  374. if err != nil {
  375. t.Fatal(err)
  376. }
  377. if s.Bytes != 0 || s.Files != 0 || s.Deleted != 0 {
  378. t.Log(s)
  379. t.Error("bad need")
  380. }
  381. // It shouldn't show up in their need list
  382. names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0))
  383. if len(names) != 0 {
  384. t.Log(names)
  385. t.Error("need no files")
  386. }
  387. // Another remote has announced it, but has set the invalid bit,
  388. // presumably it's being ignored.
  389. file = genFile("test1", 1, 103)
  390. file.SetIgnored()
  391. err = db.Update(folderID, protocol.DeviceID{43}, []protocol.FileInfo{file})
  392. if err != nil {
  393. t.Fatal(err)
  394. }
  395. // They don't need it, either
  396. s, err = db.CountNeed(folderID, protocol.DeviceID{43})
  397. if err != nil {
  398. t.Fatal(err)
  399. }
  400. if s.Bytes != 0 || s.Files != 0 || s.Deleted != 0 {
  401. t.Log(s)
  402. t.Error("bad need")
  403. }
  404. // It shouldn't show up in their need list
  405. names = mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.DeviceID{42}, config.PullOrderAlphabetic, 0, 0))
  406. if len(names) != 0 {
  407. t.Log(names)
  408. t.Error("need no files")
  409. }
  410. }
  411. func TestNeedRemoteSymlinkAndDir(t *testing.T) {
  412. t.Parallel()
  413. db, err := Open(t.TempDir())
  414. if err != nil {
  415. t.Fatal(err)
  416. }
  417. t.Cleanup(func() {
  418. if err := db.Close(); err != nil {
  419. t.Fatal(err)
  420. }
  421. })
  422. // Two remote "specials", a symlink and a directory
  423. var v protocol.Vector
  424. v.Update(1)
  425. files := []protocol.FileInfo{
  426. {Name: "sym", Type: protocol.FileInfoTypeSymlink, Sequence: 100, Version: v, Blocks: genBlocks("symlink", 0, 1)},
  427. {Name: "dir", Type: protocol.FileInfoTypeDirectory, Sequence: 101, Version: v},
  428. }
  429. err = db.Update(folderID, protocol.DeviceID{42}, files)
  430. if err != nil {
  431. t.Fatal(err)
  432. }
  433. // We need them
  434. s, err := db.CountNeed(folderID, protocol.LocalDeviceID)
  435. if err != nil {
  436. t.Fatal(err)
  437. }
  438. if s.Directories != 1 || s.Symlinks != 1 {
  439. t.Log(s)
  440. t.Error("bad need")
  441. }
  442. // They should be in the need list
  443. names := mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 0, 0))
  444. if len(names) != 2 {
  445. t.Log(names)
  446. t.Error("bad need")
  447. }
  448. }
  449. func TestNeedPagination(t *testing.T) {
  450. t.Parallel()
  451. db, err := Open(t.TempDir())
  452. if err != nil {
  453. t.Fatal(err)
  454. }
  455. t.Cleanup(func() {
  456. if err := db.Close(); err != nil {
  457. t.Fatal(err)
  458. }
  459. })
  460. // Several remote files
  461. var v protocol.Vector
  462. v.Update(1)
  463. files := []protocol.FileInfo{
  464. genFile("test0", 1, 100),
  465. genFile("test1", 1, 101),
  466. genFile("test2", 1, 102),
  467. genFile("test3", 1, 103),
  468. genFile("test4", 1, 104),
  469. genFile("test5", 1, 105),
  470. genFile("test6", 1, 106),
  471. genFile("test7", 1, 107),
  472. genFile("test8", 1, 108),
  473. genFile("test9", 1, 109),
  474. }
  475. err = db.Update(folderID, protocol.DeviceID{42}, files)
  476. if err != nil {
  477. t.Fatal(err)
  478. }
  479. // We should get the first two
  480. names := fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 2, 0)))
  481. if !slices.Equal(names, []string{"test0", "test1"}) {
  482. t.Log(names)
  483. t.Error("bad need")
  484. }
  485. // We should get the next three
  486. names = fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 3, 2)))
  487. if !slices.Equal(names, []string{"test2", "test3", "test4"}) {
  488. t.Log(names)
  489. t.Error("bad need")
  490. }
  491. // We should get the last five
  492. names = fiNames(mustCollect[protocol.FileInfo](t)(db.AllNeededGlobalFiles(folderID, protocol.LocalDeviceID, config.PullOrderAlphabetic, 5, 5)))
  493. if !slices.Equal(names, []string{"test5", "test6", "test7", "test8", "test9"}) {
  494. t.Log(names)
  495. t.Error("bad need")
  496. }
  497. }
  498. func TestDeletedAfterConflict(t *testing.T) {
  499. t.Parallel()
  500. // A delete that comes after a conflict should be applied, not lose the
  501. // conflict and suddenly cause an old conflict version to become
  502. // promoted.
  503. // D:\syncthing-windows-amd64-v2.0.0-rc.22.dev.11.gff88430e>syncthing --home=c:\PortableApp\SyncTrayzorPortable-x64\data\syncthing debug database-file tnhbr-gxtuf TreeSizeFreeSetup.exe
  504. // DEVICE TYPE NAME SEQUENCE DELETED MODIFIED SIZE FLAGS VERSION BLOCKLIST
  505. // -local- FILE TreeSizeFreeSetup.exe 499 del 2025-07-04T11:52:36.2804841Z 0 ------- HZJYWFM:1751507473,OMKHRPB:1751629956 -nil-
  506. // J5WNYJ6 FILE TreeSizeFreeSetup.exe 500 del 2025-07-04T11:52:36.2804841Z 0 ------- HZJYWFM:1751507473,OMKHRPB:1751629956 -nil-
  507. // 23NHXGS FILE TreeSizeFreeSetup.exe 445 --- 2025-06-23T03:16:10.2804841Z 13832808 -nG---- HZJYWFM:1751507473 7B4kLitF
  508. // JKX6ZDN FILE TreeSizeFreeSetup.exe 320 --- 2025-06-23T03:16:10.2804841Z 13832808 ------- JKX6ZDN:1750992570 7B4kLitF
  509. db, err := Open(t.TempDir())
  510. if err != nil {
  511. t.Fatal(err)
  512. }
  513. t.Cleanup(func() {
  514. if err := db.Close(); err != nil {
  515. t.Fatal(err)
  516. }
  517. })
  518. // A file, updated by some remote device. This file is an old, conflicted copy.
  519. file := genFile("test1", 1, 101)
  520. file.ModifiedS = 1750992570
  521. file.Version = protocol.Vector{Counters: []protocol.Counter{{ID: 5 << 60, Value: 1750992570}}}
  522. if err := db.Update(folderID, protocol.DeviceID{5}, []protocol.FileInfo{file}); err != nil {
  523. t.Fatal(err)
  524. }
  525. // The file, updated by a newer remote device. This file is the newer, conflict-winning copy.
  526. file.ModifiedS = 1751507473
  527. file.Version = protocol.Vector{Counters: []protocol.Counter{{ID: 2 << 60, Value: 1751507473}}}
  528. if err := db.Update(folderID, protocol.DeviceID{2}, []protocol.FileInfo{file}); err != nil {
  529. t.Fatal(err)
  530. }
  531. // The file, deleted locally after syncing the file from the remote above..
  532. file.SetDeleted(4)
  533. if err := db.Update(folderID, protocol.LocalDeviceID, []protocol.FileInfo{file}); err != nil {
  534. t.Fatal(err)
  535. }
  536. // The delete should be the global version
  537. f, _, err := db.GetGlobalFile(folderID, "test1")
  538. if err != nil {
  539. t.Fatal(err)
  540. }
  541. if !f.IsDeleted() {
  542. t.Log(f)
  543. t.Error("should be deleted")
  544. }
  545. }