@@ -361,6 +361,42 @@ func checkpointWALBeforeUpload(file string) error {
361361 return nil
362362}
363363
364+ // prepareTursoDBFile asks the TursoDB engine to convert the database to MVCC,
365+ // checkpoint any logical-log entries into the main file, and validate the
366+ // resulting database. Uploading only the main file is safe once all data-bearing
367+ // sidecars are empty.
368+ func prepareTursoDBFile (file string ) error {
369+ output , err := exec .Command ("tursodb" , "-q" , "-m" , "list" , file ,
370+ "PRAGMA journal_mode = mvcc; PRAGMA wal_checkpoint(TRUNCATE); PRAGMA quick_check;" ).CombinedOutput ()
371+ if err != nil {
372+ return fmt .Errorf ("could not prepare %s for TursoDB import: %w: %s" , file , err , strings .TrimSpace (string (output )))
373+ }
374+
375+ lines := strings .Fields (string (output ))
376+ if len (lines ) == 0 || lines [len (lines )- 1 ] != "ok" {
377+ return fmt .Errorf ("TursoDB integrity check failed for %s: %s" , file , strings .TrimSpace (string (output )))
378+ }
379+
380+ format , err := sniffSQLiteFileFormat (file )
381+ if err != nil {
382+ return err
383+ }
384+ if format != fileFormatMVCC {
385+ return fmt .Errorf ("TursoDB did not convert %s to MVCC format" , file )
386+ }
387+
388+ for _ , sidecar := range []struct { path , hint string }{
389+ {file + "-wal" , "close all connections to the database and retry the import" },
390+ {file + "-journal" , "the database has a leftover rollback journal; close it cleanly and retry the import" },
391+ {tursodbLogPath (file ), "close all TursoDB connections and retry the import" },
392+ } {
393+ if err := checkSidecarEmpty (sidecar .path , sidecar .hint ); err != nil {
394+ return err
395+ }
396+ }
397+ return nil
398+ }
399+
364400func checkSidecarEmpty (sidecarPath , hint string ) error {
365401 info , err := os .Stat (sidecarPath )
366402 if errors .Is (err , os .ErrNotExist ) {
@@ -389,6 +425,9 @@ func handleDBFileAWS(file string, cipher string) (*turso.DBSeed, error) {
389425 }
390426
391427 if format == fileFormatRollback {
428+ if err := checkSQLiteAvailable (); err != nil {
429+ return nil , err
430+ }
392431 // The server only accepts WAL or MVCC format files. Converting to WAL
393432 // is exactly the remediation the error message used to instruct users
394433 // to run themselves, and sqlite3 is already a hard requirement here.
@@ -403,6 +442,9 @@ func handleDBFileAWS(file string, cipher string) (*turso.DBSeed, error) {
403442
404443 switch format {
405444 case fileFormatWAL :
445+ if err := checkSQLiteAvailable (); err != nil {
446+ return nil , err
447+ }
406448 if err := checkpointWALBeforeUpload (file ); err != nil {
407449 return nil , err
408450 }
@@ -411,27 +453,13 @@ func handleDBFileAWS(file string, cipher string) (*turso.DBSeed, error) {
411453 }
412454 case fileFormatMVCC :
413455 // sqlite3-based checks can't run on MVCC (tursodb format) files: the
414- // sqlite3 binary reports them as not-a-database. The server verifies
415- // the file with the tursodb engine after upload.
456+ // sqlite3 binary reports them as not-a-database.
416457 if ! tursoDBFlag {
417458 return nil , fmt .Errorf ("%s is in tursodb (MVCC) format and can only be imported into a tursodb database" , file )
418459 }
419460 if cipher != "" {
420461 return nil , errors .New ("remote encryption is not supported when importing tursodb (MVCC) format files" )
421462 }
422- // The upload ships only the main database file: a non-empty tursodb
423- // logical log next to it means the file is not a fully checkpointed
424- // snapshot and importing it would lose the log's data.
425- if err := checkSidecarEmpty (tursodbLogPath (file ), "checkpoint the database with tursodb before importing" ); err != nil {
426- return nil , err
427- }
428- fileInfo , err := os .Stat (file )
429- if err != nil {
430- return nil , fmt .Errorf ("failed to get file info: %w" , err )
431- }
432- if fileInfo .Size () > MaxAWSDBSizeBytes {
433- return nil , errors .New ("database file size exceeds maximum allowed size of 20 GB" )
434- }
435463 case fileFormatNotSQLite :
436464 isDump , err := checkIfDump (file )
437465 if err != nil {
@@ -445,6 +473,26 @@ func handleDBFileAWS(file string, cipher string) (*turso.DBSeed, error) {
445473 return nil , fmt .Errorf ("file %s has an unsupported SQLite file format" , file )
446474 }
447475
476+ if tursoDBFlag {
477+ if err := checkTursoDBAvailable (); err != nil {
478+ return nil , err
479+ }
480+ if format != fileFormatMVCC {
481+ fmt .Printf ("Converting %s to TursoDB (MVCC) format for import.\n " , file )
482+ }
483+ if err := prepareTursoDBFile (file ); err != nil {
484+ return nil , err
485+ }
486+ }
487+
488+ fileInfo , err := os .Stat (file )
489+ if err != nil {
490+ return nil , fmt .Errorf ("failed to get file info: %w" , err )
491+ }
492+ if fileInfo .Size () > MaxAWSDBSizeBytes {
493+ return nil , errors .New ("database file size exceeds maximum allowed size of 20 GB" )
494+ }
495+
448496 seed := & turso.DBSeed {
449497 Type : "database_upload" ,
450498 Filepath : file ,
@@ -457,9 +505,6 @@ func handleDBFile(client *turso.Client, file string, isAWS bool, cipher string)
457505 if err := checkFileExists (file ); err != nil {
458506 return nil , err
459507 }
460- if err := checkSQLiteAvailable (); err != nil {
461- return nil , err
462- }
463508
464509 if isAWS {
465510 return handleDBFileAWS (file , cipher )
@@ -474,6 +519,9 @@ func handleDBFile(client *turso.Client, file string, isAWS bool, cipher string)
474519 // produce from an MVCC (tursodb format) file
475520 return nil , fmt .Errorf ("%s is in tursodb (MVCC) format and can only be imported into AWS groups" , file )
476521 }
522+ if err := checkSQLiteAvailable (); err != nil {
523+ return nil , err
524+ }
477525
478526 if err := checkSQLiteFile (file ); err != nil {
479527 return nil , err
@@ -507,6 +555,14 @@ func checkSQLiteAvailable() error {
507555 return err
508556}
509557
558+ func checkTursoDBAvailable () error {
559+ _ , err := exec .LookPath ("tursodb" )
560+ if errors .Is (err , exec .ErrNotFound ) {
561+ return errors .New ("could not find tursodb on your system. Please install it to import into a TursoDB database" )
562+ }
563+ return err
564+ }
565+
510566func checkSQLiteFile (file string ) error {
511567 output , err := exec .Command ("sqlite3" , "-list" , file , "pragma quick_check;" ).CombinedOutput ()
512568
0 commit comments