migrate-to-sqlite.pl 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. #!/usr/bin/env perl
  2. use v5.36;
  3. use re '/aa';
  4. use FindBin;
  5. use lib "$FindBin::Bin/../lib";
  6. use Trog::Config;
  7. use Trog::Data::FlatFile;
  8. use Trog::Data::SQLite;
  9. =head1 SYNOPSIS
  10. Copy a FlatFile site's posts into the SQLite data model.
  11. =head2 USAGE
  12. bin/migrate-to-sqlite.pl
  13. Takes no arguments, and must be run from the tCMS root, as the data paths are
  14. relative to it.
  15. Reads every version of every post out of data/files and writes them into
  16. data/posts.sqlite, oldest version first so that the version bookkeeping lands
  17. the same way it would have if the posts had been written there all along.
  18. Nothing is deleted: data/files is left exactly as it was, so the way back is to
  19. put data_model back to FlatFile. Once the site is running on SQLite, switch it
  20. over in config/main.cfg:
  21. [general]
  22. data_model=SQLite
  23. Safe to re-run. Posts already carried over are skipped rather than duplicated,
  24. which also makes it safe to run once against a live site and again after a
  25. final quiet period to pick up whatever was written in between.
  26. =cut
  27. my $conf = Trog::Config::get();
  28. my $from = Trog::Data::FlatFile->new($conf);
  29. my $to = Trog::Data::SQLite->new($conf);
  30. # raw, so we get the history rather than just the current version of each post.
  31. my @posts = $from->get( raw => 1, limit => 0 );
  32. die "No posts found in data/files -- is this a tCMS root?\n" unless @posts;
  33. # Oldest first. write() appends, and post_versions is maintained as it goes.
  34. @posts = sort { ( $a->{created} // 0 ) <=> ( $b->{created} // 0 ) || ( $a->{version} // 0 ) <=> ( $b->{version} // 0 ) } @posts;
  35. my ( $written, $skipped, $failed ) = ( 0, 0, 0 );
  36. foreach my $post (@posts) {
  37. if ( !$post->{id} ) {
  38. say STDERR "Skipping a post with no id (created $post->{created})";
  39. $failed++;
  40. next;
  41. }
  42. # Idempotent: (uuid, version) is unique, so this is asking the same question
  43. # the unique index would answer, just without making it an error. read()
  44. # rather than get(), which would decorate the post with its author's display
  45. # name and drag the auth database into a migration that has no use for it.
  46. my $have = $to->read( { limit => 0, id => $post->{id}, version => $post->{version} // 0 } );
  47. if (@$have) {
  48. $skipped++;
  49. next;
  50. }
  51. local $@;
  52. eval {
  53. $to->write( [$post] );
  54. $written++;
  55. 1;
  56. } or do {
  57. say STDERR "Could not write $post->{id} version " . ( $post->{version} // 0 ) . ": $@";
  58. $failed++;
  59. };
  60. }
  61. say "Carried over $written post versions, skipped $skipped already present, failed $failed.";
  62. say "Set general.data_model to SQLite in config/main.cfg to start serving from it.";
  63. exit( $failed ? 1 : 0 );