1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
5 #include "schemachecker.h"
6 #include "transaction.h"
7 #include "estringlist.h"
30 state( 0 ), substate( 0 ), revision( 0 ),
31 lock( 0 ), seq( 0 ), update( 0 ), q( 0 ),
33 result( 0 ), unparsed( 0 ), upgrade( false ), commit( true ),
34 quid( 0 ), undel( 0 ), row( 0 ), lastMailbox( 0 ), count( 0 ),
35 uidnext( 0 ), nextmodseq( 0 ), granter( 0 )
37 schema = Configuration::text( Configuration::DbSchema );
38 dbuser = Configuration::text( Configuration::DbUser ).quoted();
45 Query *lock, *seq, *update, *q;
55 // The following state variables are needed by stepTo72().
69 /*! \class Schema schema.h
71 This class manipulates the Archiveopteryx database schema. It knows
72 all the schema revisions and can upgrade a database to the latest
73 schema version automatically.
77 /*! Creates a new Schema object to check that the existing schema is one
78 that the running server understands. If \a upgrade is true (which it
79 is not, by default) and the schema is too old, it will be upgraded.
80 (If \a upgrade is false, a "please upgrade" message will be issued.)
82 If \a commit is false (which it also is not, by default), the SQL
83 statements performed during the upgrade will not be COMMITted, but
84 their success or failure will be reported.
86 The \a owner will be notified of progress via the Query returned by
90 Schema::Schema( EventHandler * owner, bool upgrade, bool commit )
93 d->result = new Query( owner );
96 d->t = new Transaction( this );
100 /*! Returns a Query object that can be used to track the progress of the
101 Schema verification or upgradation. The Query's owner is set by the
102 constructor when the Schema is created.
105 Query * Schema::result() const
111 /*! This function is responsible for checking that the running server is
112 compatible with the existing database schema, and to notify \a owner
113 when the verification is complete.
115 If the schema is not compatible, a disaster is logged.
117 The function expects to be called from ::main(), and should be the
118 first database transaction.
121 void Schema::checkRevision( EventHandler * owner )
123 Schema * s = new Schema( owner );
128 /*! After execute() has completed, this function returns the version
129 ("8.1.3") of the running Postgres server.
132 EString Schema::serverVersion() const
138 /*! Checks or upgrades the schema as required. */
140 void Schema::execute()
142 if ( d->state == 0 ) {
145 new Query( "select version() as version, revision from "
146 "mailstore for update", this );
147 d->t->enqueue( d->lock );
152 new Query( "select version() as version, revision from "
159 if ( d->state == 1 ) {
160 if ( !d->lock->done() )
163 Row *r = d->lock->nextRow();
166 = r->getEString( "version" ).simplified().section( " ", 2 );
167 d->revision = r->getInt( "revision" );
170 if ( !r || d->lock->failed() ) {
171 fail( "Bad database: Couldn't query the mailstore table.",
173 d->revision = Database::currentRevision();
177 else if ( d->revision == Database::currentRevision() ) {
179 d->l->log( "Schema is already at revision " +
180 fn( Database::currentRevision() ) +
181 ", no upgrade necessary.",
185 d->result->setState( Query::Completed );
188 else if ( d->upgrade && d->revision > Database::currentRevision() &&
189 d->revision >= 85 && Database::currentRevision() >= 80 ) {
190 d->l->log( "Downgrading schema from revision " +
191 fn( d->revision ) + " to revision " +
192 fn( Database::currentRevision() ) + ".",
196 else if ( d->upgrade && d->revision < Database::currentRevision() ) {
197 d->l->log( "Upgrading schema from revision " +
198 fn( d->revision ) + " to revision " +
199 fn( Database::currentRevision() ) + ".",
204 EString s( "The existing schema (revision " );
205 s.appendNumber( d->revision );
207 if ( d->revision < Database::currentRevision() )
211 s.append( " than this server (version " );
212 s.append( Configuration::compiledIn( Configuration::Version ) );
213 s.append( ") expected (revision " );
214 s.appendNumber( Database::currentRevision() );
215 s.append( "). Please " );
216 if ( d->revision < Database::currentRevision() ||
217 ( Database::currentRevision() >= 80 && d->revision >= 85 ) )
218 s.append( "run 'aox upgrade schema'" );
220 s.append( "upgrade" );
221 s.append( " or contact support." );
223 d->revision = Database::currentRevision();
231 d->revision > Database::currentRevision() ) {
232 while ( d->revision > Database::currentRevision() ) {
233 EString function( "downgrade_to_" + fn( d->revision - 1 ) + "()" );
234 d->l->log( "Invoking stored function " + function );
235 d->t->enqueue( new Query( "select " + function, 0 ) );
236 d->t->enqueue( new Query( "drop function " + function, 0 ) );
239 Query * q = new Query( "update mailstore set revision=$1", 0 );
240 q->bind( 1, Database::currentRevision() );
245 while ( d->revision < Database::currentRevision() ) {
246 if ( d->state == 2 ) {
252 if ( d->state == 3 ) {
254 new Query( "update mailstore set revision=revision+1",
256 d->t->enqueue( d->update );
261 if ( d->state == 4 ) {
262 if ( !d->update->done() )
265 d->l->log( "Done.", Log::Debug );
270 if ( d->revision == Database::currentRevision() ) {
277 if ( d->state == 5 ) {
278 if ( d->dbuser.unquoted() ==
279 Configuration::text( Configuration::DbOwner ) ) {
280 d->l->log( "Warning: db-user is the same as db-owner",
284 d->l->log( "Checking database, adjusting privileges.",
286 Granter * g = new Granter( d->dbuser.unquoted(), d->t );
289 // SchemaChecker * c = new SchemaChecker( d->t );
299 if ( d->state == 6 ) {
303 if ( !d->unparsed && !d->t->failed() && d->upgrade ) {
304 d->unparsed = new Query( "select count(*) as unparsed "
305 "from unparsed_messages", this );
306 d->unparsed->execute();
309 if ( d->unparsed && !d->unparsed->done() )
312 if ( d->unparsed && d->unparsed->hasResults() ) {
313 Row * r = d->unparsed->nextRow();
314 int64 u = r->getBigint( "unparsed" );
316 d->l->log( "Please run 'aox reparse' (or 'aox reparse -e') "
317 "when Archiveopteryx has been started. "
318 "There are " + fn( u ) + " unparsed messages now. ",
325 if ( d->state == 7 ) {
329 if ( d->t->failed() && !d->result->failed() ) {
332 s = "The schema could not be upgraded to revision " +
333 fn( Database::currentRevision() ) + ".";
335 s = "The schema could not be validated.";
336 fail( s, d->t->failedQuery() );
338 else if ( d->upgrade ) {
339 EString s( "Schema upgraded to revision " );
340 s.appendNumber( Database::currentRevision() );
342 s.append( ", but not committed" );
346 d->l->log( s, Log::Significant );
347 d->result->setState( Query::Completed );
353 if ( d->state == 8 ) {
360 /*! This private helper logs a \a description of the step currently
364 void Schema::describeStep( const EString & description )
366 d->l->log( fn( d->revision ) + "-" + fn( d->revision + 1 ) + ": " +
367 description, Log::Significant );
371 /*! Given an error message \a s and, optionally, the query \a q that
372 caused the error, this private helper function logs a suitable set
373 of Disaster messages (including the Query::description()) and sets
374 the error message for d->result to \a s.
377 void Schema::fail( const EString &s, Query * q )
379 d->result->setError( s );
380 d->l->log( s, Log::Disaster );
382 d->l->log( "Query: " + q->description(), Log::Disaster );
383 d->l->log( "Error: " + q->error(), Log::Disaster );
388 #include "downgrades.inc"
391 /*! Uses a helper function to upgrade the schema from d->revision to
392 d->revision+1. Returns false if the helper has not yet completed
396 bool Schema::singleStep()
400 switch ( d->revision ) {
402 c = stepTo2(); break;
404 c = stepTo3(); break;
406 c = stepTo4(); break;
408 c = stepTo5(); break;
410 c = stepTo6(); break;
412 c = stepTo7(); break;
414 c = stepTo8(); break;
416 c = stepTo9(); break;
418 c = stepTo10(); break;
420 c = stepTo11(); break;
422 c = stepTo12(); break;
424 c = stepTo13(); break;
426 c = stepTo14(); break;
428 c = stepTo15(); break;
430 c = stepTo16(); break;
432 c = stepTo17(); break;
434 c = stepTo18(); break;
436 c = stepTo19(); break;
438 c = stepTo20(); break;
440 c = stepTo21(); break;
442 c = stepTo22(); break;
444 c = stepTo23(); break;
446 c = stepTo24(); break;
448 c = stepTo25(); break;
450 c = stepTo26(); break;
452 c = stepTo27(); break;
454 c = stepTo28(); break;
456 c = stepTo29(); break;
458 c = stepTo30(); break;
460 c = stepTo31(); break;
462 c = stepTo32(); break;
464 c = stepTo33(); break;
466 c = stepTo34(); break;
468 c = stepTo35(); break;
470 c = stepTo36(); break;
472 c = stepTo37(); break;
474 c = stepTo38(); break;
476 c = stepTo39(); break;
478 c = stepTo40(); break;
480 c = stepTo41(); break;
482 c = stepTo42(); break;
484 c = stepTo43(); break;
486 c = stepTo44(); break;
488 c = stepTo45(); break;
490 c = stepTo46(); break;
492 c = stepTo47(); break;
494 c = stepTo48(); break;
496 c = stepTo49(); break;
498 c = stepTo50(); break;
500 c = stepTo51(); break;
502 c = stepTo52(); break;
504 c = stepTo53(); break;
506 c = stepTo54(); break;
508 c = stepTo55(); break;
510 c = stepTo56(); break;
512 c = stepTo57(); break;
514 c = stepTo58(); break;
516 c = stepTo59(); break;
518 c = stepTo60(); break;
520 c = stepTo61(); break;
522 c = stepTo62(); break;
524 c = stepTo63(); break;
526 c = stepTo64(); break;
528 c = stepTo65(); break;
530 c = stepTo66(); break;
532 c = stepTo67(); break;
534 c = stepTo68(); break;
536 c = stepTo69(); break;
538 c = stepTo70(); break;
540 c = stepTo71(); break;
542 c = stepTo72(); break;
544 c = stepTo73(); break;
546 c = stepTo74(); break;
548 c = stepTo75(); break;
550 c = stepTo76(); break;
552 c = stepTo77(); break;
554 c = stepTo78(); break;
556 c = stepTo79(); break;
558 c = stepTo80(); break;
560 c = stepTo81(); break;
562 c = stepTo82(); break;
564 c = stepTo83(); break;
566 c = stepTo84(); break;
568 c = stepTo85(); break;
570 c = stepTo86(); break;
572 c = stepTo87(); break;
574 c = stepTo88(); break;
576 c = stepTo89(); break;
578 c = stepTo90(); break;
580 c = stepTo91(); break;
582 c = stepTo92(); break;
584 c = stepTo93(); break;
586 c = stepTo94(); break;
588 c = stepTo95(); break;
590 c = stepTo96(); break;
592 c = stepTo97(); break;
594 d->l->log( "Internal error. Reached impossible revision " +
595 fn( d->revision ) + ".", Log::Disaster );
599 if ( d->revision >= 89 &&
600 d->revision <= numDowngradeFunctions &&
601 downgradeFunctions[d->revision] )
602 d->t->enqueue( downgradeFunctions[d->revision] );
608 /*! Changes the type of users.login and users.secret to text to remove
609 the made-up length restriction on the earlier varchar field.
612 bool Schema::stepTo2()
614 if ( d->substate == 0 ) {
615 describeStep( "Changing users.login/secret to text." );
616 d->q = new Query( "alter table users add login2 text", this );
617 d->t->enqueue( d->q );
618 d->q = new Query( "update users set login2=login", this );
619 d->t->enqueue( d->q );
620 d->q = new Query( "alter table users drop login", this );
621 d->t->enqueue( d->q );
622 d->q = new Query( "alter table users rename login2 to login",
624 d->t->enqueue( d->q );
625 d->q = new Query( "alter table users add unique(login)",
627 d->t->enqueue( d->q );
628 d->q = new Query( "alter table users add secret2 text", this );
629 d->t->enqueue( d->q );
630 d->q = new Query( "update users set secret2=secret", this );
631 d->t->enqueue( d->q );
632 d->q = new Query( "alter table users drop secret", this );
633 d->t->enqueue( d->q );
634 d->q = new Query( "alter table users rename secret2 to secret",
636 d->t->enqueue( d->q );
641 if ( d->substate == 1 ) {
650 /*! Merges the binary_parts table into bodyparts. */
652 bool Schema::stepTo3()
654 if ( d->substate == 0 ) {
655 describeStep( "Merging bodyparts and binary_parts." );
656 d->q = new Query( "alter table bodyparts add hash text", this );
657 d->t->enqueue( d->q );
658 d->q = new Query( "alter table bodyparts add data bytea", this );
659 d->t->enqueue( d->q );
660 d->q = new Query( "alter table bodyparts add text2 text", this );
661 d->t->enqueue( d->q );
662 d->q = new Query( "update bodyparts set data=b.data from "
663 "binary_parts b where id=b.bodypart", this );
664 d->t->enqueue( d->q );
665 d->q = new Query( "declare parts cursor for "
666 "select id,text,data from bodyparts", this );
667 d->t->enqueue( d->q );
668 d->q = new Query( "fetch 512 from parts", this );
669 d->t->enqueue( d->q );
674 if ( d->substate == 1 ) {
675 while ( d->q->hasResults() ) {
676 Row *r = d->q->nextRow();
680 new Query( "update bodyparts set "
681 "text2=$1,hash=$2 where id=$3", this );
682 if ( r->isNull( "text" ) ) {
683 data = r->getEString( "data" );
685 u->bind( 2, MD5::hash( data ).hex() );
688 text = r->getEString( "text" );
690 u->bind( 2, MD5::hash( text ).hex() );
692 u->bind( 3, r->getInt( "id" ) );
699 if ( d->q->rows() != 0 ) {
700 d->q = new Query( "fetch 512 from parts", this );
701 d->t->enqueue( d->q );
707 d->t->enqueue( new Query( "close parts", this ) );
711 if ( d->substate == 2 ) {
712 d->q = new Query( "alter table bodyparts drop text", this );
713 d->t->enqueue( d->q );
714 d->q = new Query( "alter table bodyparts rename text2 to text", this );
715 d->t->enqueue( d->q );
716 d->q = new Query( "select id,hash from bodyparts where hash in "
717 "(select hash from bodyparts group by hash"
718 " having count(*) > 1)", this );
719 d->t->enqueue( d->q );
724 if ( d->substate == 3 ) {
731 while ( d->q->hasResults() ) {
732 Row *r = d->q->nextRow();
733 uint id = r->getInt( "id" );
734 EString hash = r->getEString( "hash" );
736 uint *old = hashes.find( hash );
738 ids.append( fn( id ) );
740 new Query( "update part_numbers set "
741 "bodypart=$1 where bodypart=$2", this );
748 = (uint*)Allocator::alloc( sizeof(uint), 0 );
750 hashes.insert( hash, tmp );
754 if ( !ids.isEmpty() ) {
755 d->q = new Query( "delete from bodyparts where id in "
756 "(" + ids.join(",") + ")", this );
757 d->t->enqueue( d->q );
759 d->q = new Query( "drop table binary_parts", this );
760 d->t->enqueue( d->q );
761 d->q = new Query( "alter table bodyparts add unique(hash)", this );
762 d->t->enqueue( d->q );
767 if ( d->substate == 4 ) {
776 /*! Move message flags from the messages table to the extra_flags table,
777 now renamed just "flags".
780 bool Schema::stepTo4()
782 if ( d->substate == 0 ) {
783 describeStep( "Creating flags from messages/extra_flags." );
784 d->q = new Query( "alter table extra_flags rename to flags", this );
785 d->t->enqueue( d->q );
786 d->q = new Query( "insert into flag_names (name) values ($1)", this );
787 d->q->bind( 1, "\\Deleted" );
788 d->t->enqueue( d->q );
789 d->q = new Query( "insert into flag_names (name) values ($1)", this );
790 d->q->bind( 1, "\\Answered" );
791 d->t->enqueue( d->q );
792 d->q = new Query( "insert into flag_names (name) values ($1)", this );
793 d->q->bind( 1, "\\Flagged" );
794 d->t->enqueue( d->q );
795 d->q = new Query( "insert into flag_names (name) values ($1)", this );
796 d->q->bind( 1, "\\Draft" );
797 d->t->enqueue( d->q );
798 d->q = new Query( "insert into flag_names (name) values ($1)", this );
799 d->q->bind( 1, "\\Seen" );
800 d->t->enqueue( d->q );
801 d->q = new Query( "insert into flags (mailbox,uid,flag) "
802 "select mailbox,uid,"
803 "(select id from flag_names"
804 " where name='\\Deleted') from messages "
805 "where deleted", this );
806 d->t->enqueue( d->q );
807 d->q = new Query( "insert into flags (mailbox,uid,flag) "
808 "select mailbox,uid,"
809 "(select id from flag_names"
810 " where name='\\Answered') from messages "
811 "where answered", this );
812 d->t->enqueue( d->q );
813 d->q = new Query( "insert into flags (mailbox,uid,flag) "
814 "select mailbox,uid,"
815 "(select id from flag_names"
816 " where name='\\Flagged') from messages "
817 "where flagged", this );
818 d->t->enqueue( d->q );
819 d->q = new Query( "insert into flags (mailbox,uid,flag) "
820 "select mailbox,uid,"
821 "(select id from flag_names"
822 " where name='\\Draft') from messages "
823 "where draft", this );
824 d->t->enqueue( d->q );
825 d->q = new Query( "insert into flags (mailbox,uid,flag) "
826 "select mailbox,uid,"
827 "(select id from flag_names"
828 " where name='\\Seen') from messages "
829 "where seen", this );
830 d->t->enqueue( d->q );
831 d->q = new Query( "alter table messages drop deleted", this );
832 d->t->enqueue( d->q );
833 d->q = new Query( "alter table messages drop answered", this );
834 d->t->enqueue( d->q );
835 d->q = new Query( "alter table messages drop flagged", this );
836 d->t->enqueue( d->q );
837 d->q = new Query( "alter table messages drop draft", this );
838 d->t->enqueue( d->q );
839 d->q = new Query( "alter table messages drop seen", this );
840 d->t->enqueue( d->q );
845 if ( d->substate == 1 ) {
854 /*! Add some indices on header_fields, address_fields, and flags. */
856 bool Schema::stepTo5()
858 if ( d->substate == 0 ) {
859 describeStep( "Adding hf_mup, af_mu, fl_mu indices." );
860 d->q = new Query( "create index hf_mup on "
861 "header_fields (mailbox,uid,part)", this );
862 d->t->enqueue( d->q );
863 d->q = new Query( "create index af_mu on "
864 "address_fields (mailbox,uid)", this );
865 d->t->enqueue( d->q );
866 d->q = new Query( "create index fl_mu on flags (mailbox,uid)",
868 d->t->enqueue( d->q );
873 if ( d->substate == 1 ) {
882 /*! Move bodyparts.bytes/lines to the part_numbers table. */
884 bool Schema::stepTo6()
886 if ( d->substate == 0 ) {
887 describeStep( "Moving bytes/lines to part_numbers." );
888 d->q = new Query( "alter table part_numbers add bytes integer",
890 d->t->enqueue( d->q );
891 d->q = new Query( "alter table part_numbers add lines integer",
893 d->t->enqueue( d->q );
894 d->q = new Query( "update part_numbers set bytes=bodyparts.bytes,"
895 "lines=bodyparts.lines from bodyparts where "
896 "part_numbers.bodypart=bodyparts.id", this );
897 d->t->enqueue( d->q );
898 d->q = new Query( "alter table part_numbers alter bodypart "
899 "drop not null", this );
900 d->t->enqueue( d->q );
901 d->q = new Query( "alter table bodyparts drop lines", this );
902 d->t->enqueue( d->q );
907 if ( d->substate == 1 ) {
916 /*! Add header_fields.position. */
918 bool Schema::stepTo7()
920 if ( d->substate == 0 ) {
921 describeStep( "Adding header_fields.position." );
922 d->q = new Query( "alter table header_fields add "
923 "position integer", this );
924 d->t->enqueue( d->q );
925 d->q = new Query( "alter table header_fields alter part "
926 "set not null", this );
927 d->t->enqueue( d->q );
928 d->q = new Query( "create temporary sequence hf_pos", this );
929 d->t->enqueue( d->q );
930 d->q = new Query( "declare groups cursor for "
931 "select distinct mailbox,uid,part "
932 "from header_fields", this );
933 d->t->enqueue( d->q );
934 d->q = new Query( "fetch 512 from groups", this );
935 d->t->enqueue( d->q );
940 if ( d->substate == 1 ) {
941 while ( d->q->hasResults() ) {
942 Row *r = d->q->nextRow();
945 new Query( "update header_fields set position="
946 "nextval('hf_pos') where id in "
947 "(select id from header_fields "
948 "where not (mailbox,uid,part) is "
949 "distinct from ($1,$2,$3) order by id)",
951 u->bind( 1, r->getInt( "mailbox" ) );
952 u->bind( 2, r->getInt( "uid" ) );
953 u->bind( 3, r->getEString( "part" ) );
956 u = new Query( "alter sequence hf_pos restart with 1", this );
963 if ( d->q->rows() != 0 ) {
964 d->q = new Query( "fetch 512 from groups", this );
965 d->t->enqueue( d->q );
970 d->t->enqueue( new Query( "close groups", this ) );
971 d->q = new Query( "alter table header_fields add unique "
972 "(mailbox,uid,part,position,field)", this );
973 d->t->enqueue( d->q );
979 if ( d->substate == 2 ) {
988 /*! Make address_fields refer to header_fields. */
990 bool Schema::stepTo8()
992 if ( d->substate == 0 ) {
993 d->l->log( "Making address_fields refer to header_fields." );
994 d->q = new Query( "delete from address_fields", this );
995 d->t->enqueue( d->q );
996 d->q = new Query( "alter table address_fields drop field", this );
997 d->t->enqueue( d->q );
998 d->q = new Query( "alter table address_fields add part text", this );
999 d->t->enqueue( d->q );
1000 d->q = new Query( "alter table address_fields alter part "
1001 "set not null", this );
1002 d->t->enqueue( d->q );
1003 d->q = new Query( "alter table address_fields add "
1004 "position integer", this );
1005 d->t->enqueue( d->q );
1006 d->q = new Query( "alter table address_fields alter "
1007 "position set not null", this );
1008 d->t->enqueue( d->q );
1009 d->q = new Query( "alter table address_fields add "
1010 "field integer", this );
1011 d->t->enqueue( d->q );
1012 d->q = new Query( "alter table address_fields alter field "
1013 "set not null", this );
1014 d->t->enqueue( d->q );
1015 d->q = new Query( "alter table address_fields add foreign key "
1016 "(mailbox,uid,part,position,field) "
1017 "references header_fields "
1018 "(mailbox,uid,part,position,field) "
1019 "on delete cascade", this );
1020 d->t->enqueue( d->q );
1025 if ( d->substate == 1 ) {
1026 if ( !d->q->done() )
1034 /*! Remove the recent_messages table altogether. */
1036 bool Schema::stepTo9()
1038 if ( d->substate == 0 ) {
1039 describeStep( "Removing recent_messages." );
1040 d->q = new Query( "alter table mailboxes add "
1041 "first_recent integer ", this );
1042 d->t->enqueue( d->q );
1043 d->q = new Query( "update mailboxes set "
1044 "first_recent=coalesce((select min(uid) "
1045 "from recent_messages where "
1046 "mailbox=mailboxes.id),uidnext)", this );
1047 d->t->enqueue( d->q );
1048 d->q = new Query( "alter table mailboxes alter first_recent "
1049 "set not null", this );
1050 d->t->enqueue( d->q );
1051 d->q = new Query( "alter table mailboxes alter first_recent "
1052 "set default 1", this );
1053 d->t->enqueue( d->q );
1054 d->q = new Query( "drop table recent_messages", this );
1055 d->t->enqueue( d->q );
1060 if ( d->substate == 1 ) {
1061 if ( !d->q->done() )
1069 /*! Add "on delete cascade" to the mailboxes.owner reference. */
1071 bool Schema::stepTo10()
1073 if ( d->substate == 0 ) {
1074 describeStep( "Altering mailboxes_owner_fkey." );
1076 EString constraint = "mailboxes_owner_fkey";
1077 if ( d->version.startsWith( "7" ) )
1080 d->q = new Query( "alter table mailboxes drop constraint "
1081 "\"" + constraint + "\"", this );
1082 d->t->enqueue( d->q );
1083 d->q = new Query( "alter table mailboxes add constraint "
1084 "mailboxes_owner_fkey foreign key "
1085 "(owner) references users(id) "
1086 "on delete cascade", this );
1087 d->t->enqueue( d->q );
1092 if ( d->substate == 1 ) {
1093 if ( !d->q->done() )
1101 /*! Delete the revisions sequence. */
1103 bool Schema::stepTo11()
1105 if ( d->substate == 0 ) {
1106 describeStep( "Deleting revisions." );
1107 d->q = new Query( "drop sequence revisions", this );
1108 d->t->enqueue( d->q );
1113 if ( d->substate == 1 ) {
1114 if ( !d->q->done() )
1122 /*! Reverse stepTo10(). We don't want to delete rows in mailboxes. */
1124 bool Schema::stepTo12()
1126 if ( d->substate == 0 ) {
1127 describeStep( "Reverting mailboxes_owner_fkey change." );
1128 d->q = new Query( "alter table mailboxes drop constraint "
1129 "\"mailboxes_owner_fkey\"", this );
1130 d->t->enqueue( d->q );
1131 d->q = new Query( "alter table mailboxes add constraint "
1132 "mailboxes_owner_fkey foreign key "
1133 "(owner) references users(id)", this );
1134 d->t->enqueue( d->q );
1139 if ( d->substate == 1 ) {
1140 if ( !d->q->done() )
1148 /*! Create the annotation_names and annotations tables. */
1150 bool Schema::stepTo13()
1152 if ( d->substate == 0 ) {
1153 describeStep( "Creating annotations/annotation_names." );
1154 d->q = new Query( "create table annotation_names"
1155 "(id serial primary key, name text unique)",
1157 d->t->enqueue( d->q );
1158 d->q = new Query( "create table annotations"
1159 "(mailbox integer not null,uid integer not null,"
1160 "owner integer references users(id),name integer "
1161 "not null references annotation_names(id),"
1162 "value text,type text,language text,"
1164 "unique(mailbox,uid,owner,name),"
1165 "foreign key (mailbox,uid) references "
1166 "messages(mailbox,uid) on delete cascade)",
1168 d->t->enqueue( d->q );
1173 if ( d->substate == 1 ) {
1174 if ( !d->q->done() )
1182 /*! Add the tables required to support views. */
1184 bool Schema::stepTo14()
1186 if ( d->substate == 0 ) {
1187 describeStep( "Creating views/view_messages." );
1188 d->q = new Query( "create table views ("
1189 "id serial primary key,"
1190 "source integer not null references mailboxes(id) "
1191 "on delete cascade,"
1192 "view integer not null references mailboxes(id) "
1193 "on delete cascade unique,"
1194 "suidnext integer not null,"
1195 "selector text)", this );
1196 d->t->enqueue( d->q );
1197 d->q = new Query( "create table view_messages ("
1198 "view integer not null references views(view) "
1199 "on delete cascade,"
1200 "uid integer not null,"
1201 "source integer not null,"
1202 "suid integer not null,"
1203 "foreign key (source, suid) "
1204 "references messages(mailbox, uid) "
1205 "on delete cascade)", this );
1206 d->t->enqueue( d->q );
1211 if ( d->substate == 1 ) {
1212 if ( !d->q->done() )
1220 /*! Add "on delete cascade" to the subscriptions/annotations.owner
1224 bool Schema::stepTo15()
1226 if ( d->substate == 0 ) {
1227 describeStep( "Altering subscriptions_owner_fkey." );
1229 EString ca( "subscriptions_owner_fkey" );
1230 EString cb( "annotations_owner_fkey" );
1231 if ( d->version.startsWith( "7" ) ) {
1236 d->q = new Query( "alter table subscriptions drop constraint "
1237 "\"" + ca + "\"", this );
1238 d->t->enqueue( d->q );
1239 d->q = new Query( "alter table subscriptions add constraint "
1240 "subscriptions_owner_fkey foreign key "
1241 "(owner) references users(id) "
1242 "on delete cascade", this );
1243 d->t->enqueue( d->q );
1244 d->q = new Query( "alter table annotations drop constraint "
1245 "\"" + cb + "\"", this );
1246 d->t->enqueue( d->q );
1247 d->q = new Query( "alter table annotations add constraint "
1248 "annotations_owner_fkey foreign key "
1249 "(owner) references users(id) "
1250 "on delete cascade", this );
1251 d->t->enqueue( d->q );
1256 if ( d->substate == 1 ) {
1257 if ( !d->q->done() )
1265 /*! Add the aliases table. */
1267 bool Schema::stepTo16()
1269 if ( d->substate == 0 ) {
1270 describeStep( "Creating aliases table." );
1271 d->q = new Query( "create table aliases (address text,mailbox "
1272 "integer not null references mailboxes(id))",
1274 d->t->enqueue( d->q );
1279 if ( d->substate == 1 ) {
1280 if ( !d->q->done() )
1288 /*! Drop the aliases table from #16 (never released) and recreate it,
1289 with a reference to the address, and a link from users.
1292 bool Schema::stepTo17()
1294 if ( d->substate == 0 ) {
1295 describeStep( "Recreating unified aliases table." );
1296 d->q = new Query( "drop table aliases", this );
1297 d->t->enqueue( d->q );
1298 d->q = new Query( "create table aliases (id serial primary key, "
1299 "address integer not null unique references "
1300 "addresses(id), mailbox integer not null "
1301 "references mailboxes(id))", this );
1302 d->t->enqueue( d->q );
1303 d->q = new Query( "insert into aliases (address, mailbox) "
1304 "select address,inbox from users", this );
1305 d->t->enqueue( d->q );
1306 d->q = new Query( "alter table users add alias integer "
1307 "references aliases(id)", this );
1308 d->t->enqueue( d->q );
1309 d->q = new Query( "update users set alias=(select id from aliases "
1310 "where aliases.address=users.address and "
1311 "mailbox=inbox)", this );
1312 d->t->enqueue( d->q );
1313 d->q = new Query( "alter table users alter alias set not null", this );
1314 d->t->enqueue( d->q );
1315 d->q = new Query( "alter table users drop address", this );
1316 d->t->enqueue( d->q );
1317 d->q = new Query( "alter table users drop inbox", this );
1318 d->t->enqueue( d->q );
1323 if ( d->substate == 1 ) {
1324 if ( !d->q->done() )
1332 /*! Add the scripts table. */
1334 bool Schema::stepTo18()
1336 if ( d->substate == 0 ) {
1337 describeStep( "Creating scripts table." );
1338 d->q = new Query( "create table scripts (id serial primary key,"
1339 "owner integer not null references users(id),"
1340 "name text, active boolean not null default 'f',"
1341 "script text)", this );
1342 d->t->enqueue( d->q );
1347 if ( d->substate == 1 ) {
1348 if ( !d->q->done() )
1356 /*! Add the date_fields table. */
1358 bool Schema::stepTo19()
1360 if ( d->substate == 0 ) {
1361 describeStep( "Creating date_fields table." );
1362 d->q = new Query( "create table date_fields (mailbox "
1363 "integer not null, uid integer not null, "
1364 "value timestamp with time zone, "
1365 "foreign key (mailbox,uid) references "
1366 "messages(mailbox,uid) on delete cascade )",
1368 d->t->enqueue( d->q );
1373 if ( d->substate == 1 ) {
1374 if ( !d->q->done() )
1382 /*! Populate the date_fields table from header_fields. */
1384 bool Schema::stepTo20()
1386 describeStep( "(Not) Populating the date_fields table." );
1391 /*! Remove unnecessary stuff from annotations. */
1393 bool Schema::stepTo21()
1395 if ( d->substate == 0 ) {
1396 describeStep( "Removing fields from annotations table." );
1397 d->q = new Query( "alter table annotations drop type", this );
1398 d->t->enqueue( d->q );
1399 d->q = new Query( "alter table annotations drop language", this );
1400 d->t->enqueue( d->q );
1401 d->q = new Query( "alter table annotations drop displayname", this );
1402 d->t->enqueue( d->q );
1407 if ( d->substate == 1 ) {
1408 if ( !d->q->done() )
1416 /*! For any two flag names that differ only in case, moves all flags
1417 from one to the other and removes the unused one. Then adds an
1418 index to ensure uniqueness in the future.
1421 bool Schema::stepTo22()
1423 if ( d->substate == 0 ) {
1424 describeStep( "Finding flag names that differ only in case." );
1425 d->q = new Query( "select a.id as to, b.id as from, a.name as name "
1426 "from flag_names a, flag_names b "
1427 "where a.id < b.id and lower(a.name)=lower(b.name) "
1428 "order by a.id, b.id", this );
1429 d->t->enqueue( d->q );
1434 if ( d->substate == 1 ) {
1435 if ( !d->q->done() )
1438 if ( d->q->failed() ) {
1439 d->l->log( "Internal error.", Log::Debug );
1444 d->l->log( "Changing case for " + fn( d->q->rows() ) + " flags.",
1448 while ( (r=d->q->nextRow()) != 0 ) {
1449 d->l->log( "Unbreaking " + r->getEString( "name" ) + ".",
1453 q = new Query( "update flags set flag=$1 where flag=$2", 0 );
1454 q->bind( 1, r->getInt( "to" ) );
1455 q->bind( 2, r->getInt( "from" ) );
1458 q = new Query( "delete from flag_names where id=$1", 0 );
1459 q->bind( 1, r->getInt( "from" ) );
1462 d->q = new Query( "alter table flag_names drop constraint "
1463 "flag_names_name_key", this );
1464 d->t->enqueue( d->q );
1465 d->q = new Query( "create unique index fn_uname on "
1466 "flag_names (lower(name))", this );
1467 d->t->enqueue( d->q );
1472 if ( d->substate == 2 ) {
1473 if ( !d->q->done() )
1481 /*! Add the deleted_messages table. */
1483 bool Schema::stepTo23()
1485 if ( d->substate == 0 ) {
1486 describeStep( "Creating deleted_messages table." );
1487 d->q = new Query( "create table deleted_messages (mailbox "
1488 "integer not null, uid integer not null, "
1489 "deleted_by integer not null references "
1490 "users(id), deleted_at timestamp not null "
1491 "default current_timestamp, reason text, "
1492 "foreign key (mailbox,uid) references "
1493 "messages(mailbox,uid) on delete cascade )",
1495 d->t->enqueue( d->q );
1500 if ( d->substate == 1 ) {
1501 if ( !d->q->done() )
1509 /*! Create threads/thread_members if they don't exist already. */
1511 bool Schema::stepTo24()
1513 if ( d->substate == 0 ) {
1514 describeStep( "Creating threads/thread_message" );
1515 d->q = new Query( "select * from information_schema.tables where "
1516 "table_name='threads'", this );
1517 d->t->enqueue( d->q );
1522 if ( d->substate == 1 ) {
1523 if ( !d->q->done() )
1525 if ( !d->q->hasResults() ) {
1526 d->q = new Query( "create table threads (id serial primary "
1527 "key,mailbox integer not null references "
1528 "mailboxes(id),subject text unique)",
1530 d->t->enqueue( d->q );
1531 d->q = new Query( "create table thread_members (thread integer "
1532 "not null references threads(id),mailbox integer "
1533 "not null,uid integer not null,foreign key "
1534 "(mailbox,uid) references messages(mailbox,uid) "
1535 "on delete cascade)", this );
1536 d->t->enqueue( d->q );
1545 if ( d->substate == 2 ) {
1546 if ( !d->q->done() )
1554 /*! Adds the modsequences table. */
1556 bool Schema::stepTo25()
1558 if ( d->substate == 0 ) {
1559 describeStep( "Creating modsequences table." );
1560 d->q = new Query( "create sequence nextmodsequence", this );
1561 d->t->enqueue( d->q );
1562 d->q = new Query( "grant select,update on nextmodsequence to " +
1564 d->t->enqueue( d->q );
1565 d->q = new Query( "create table modsequences (mailbox integer "
1566 "not null, uid integer not null, modseq bigint "
1567 "not null, foreign key (mailbox, uid) references "
1568 "messages(mailbox, uid))", this );
1569 d->t->enqueue( d->q );
1570 d->q = new Query( "grant select,insert,update on modsequences to " +
1572 d->t->enqueue( d->q );
1577 if ( d->substate == 1 ) {
1578 if ( !d->q->done() )
1587 /*! Alters deleted_messages.deleted_at to be a timestamp with time zone. */
1589 bool Schema::stepTo26()
1591 if ( d->substate == 0 ) {
1592 describeStep( "Altering deleted_messages.deleted_at to timestamptz." );
1593 d->q = new Query( "alter table deleted_messages add dtz timestamp "
1594 "with time zone", this );
1595 d->t->enqueue( d->q );
1596 d->q = new Query( "update deleted_messages set dtz=deleted_at", this );
1597 d->t->enqueue( d->q );
1598 d->q = new Query( "alter table deleted_messages alter dtz set "
1599 "default current_timestamp", this );
1600 d->t->enqueue( d->q );
1601 d->q = new Query( "alter table deleted_messages alter dtz set "
1603 d->t->enqueue( d->q );
1604 d->q = new Query( "alter table deleted_messages drop deleted_at",
1606 d->t->enqueue( d->q );
1607 d->q = new Query( "alter table deleted_messages rename dtz to "
1608 "deleted_at", this );
1609 d->t->enqueue( d->q );
1614 if ( d->substate == 1 ) {
1615 if ( !d->q->done() )
1623 /*! Add "on delete cascade" to the mailboxes.owner reference. */
1625 bool Schema::stepTo27()
1627 if ( d->substate == 0 ) {
1628 describeStep( "Altering modsequences_mailbox_fkey." );
1630 EString constraint = "modsequences_mailbox_fkey";
1631 if ( d->version.startsWith( "7" ) )
1634 d->q = new Query( "alter table modsequences drop constraint "
1635 "\"" + constraint + "\"", this );
1636 d->t->enqueue( d->q );
1637 d->q = new Query( "alter table modsequences add constraint "
1638 "modsequences_mailbox_fkey foreign key "
1639 "(mailbox,uid) references "
1640 "messages(mailbox,uid) "
1641 "on delete cascade", this );
1642 d->t->enqueue( d->q );
1647 if ( d->substate == 1 ) {
1648 if ( !d->q->done() )
1656 /*! Create the deliveries table. */
1658 bool Schema::stepTo28()
1660 if ( d->substate == 0 ) {
1661 describeStep( "Creating deliveries table." );
1662 d->q = new Query( "create table deliveries (id serial primary key,"
1663 "recipient integer not null references addresses(id),"
1664 "mailbox integer not null, uid integer not null,"
1665 "injected_at timestamp with time zone,"
1666 "expires_at timestamp with time zone,"
1667 "foreign key (mailbox, uid) references "
1668 "messages(mailbox, uid) on delete cascade)", this );
1669 d->t->enqueue( d->q );
1670 d->q = new Query( "grant select,insert,update,delete "
1671 "on deliveries to " + d->dbuser, this );
1672 d->t->enqueue( d->q );
1673 d->q = new Query( "grant select,update on deliveries_id_seq "
1674 "to " + d->dbuser, this );
1675 d->t->enqueue( d->q );
1680 if ( d->substate == 1 ) {
1681 if ( !d->q->done() )
1689 /*! Replace views.suidnext with nextmodseq. */
1691 bool Schema::stepTo29()
1693 if ( d->substate == 0 ) {
1694 describeStep( "Replacing views.suidnext with nextmodseq." );
1695 d->q = new Query( "alter table views add nextmodseq bigint", this );
1696 d->t->enqueue( d->q );
1697 d->q = new Query( "update views set "
1698 "nextmodseq=nextval('nextmodsequence')", this );
1699 d->t->enqueue( d->q );
1700 d->q = new Query( "alter table views alter nextmodseq "
1701 "set not null", this );
1702 d->t->enqueue( d->q );
1703 d->q = new Query( "alter table views drop suidnext", this );
1704 d->t->enqueue( d->q );
1709 if ( d->substate == 1 ) {
1710 if ( !d->q->done() )
1718 /*! Create the access_keys table. */
1720 bool Schema::stepTo30()
1722 if ( d->substate == 0 ) {
1723 describeStep( "Creating access_keys table." );
1724 d->q = new Query( "create table access_keys (userid integer not null "
1725 "references users(id) on delete cascade, mailbox "
1726 "integer not null references mailboxes(id) on "
1727 "delete cascade, key text not null, "
1728 "primary key (userid, mailbox))", this );
1729 d->t->enqueue( d->q );
1730 d->q = new Query( "grant select,insert,delete on access_keys "
1731 "to " + d->dbuser, this );
1732 d->t->enqueue( d->q );
1737 if ( d->substate == 1 ) {
1738 if ( !d->q->done() )
1746 /*! Add indexes on addresses and deleted_messages. */
1748 bool Schema::stepTo31()
1750 if ( d->substate == 0 ) {
1751 describeStep( "Adding indexes on addresses and deleted_messages." );
1752 d->q = new Query( "create index ald on addresses(lower(localpart), "
1753 "lower(domain))", this );
1754 d->t->enqueue( d->q );
1755 d->q = new Query( "analyse addresses", this );
1756 d->t->enqueue( d->q );
1757 d->q = new Query( "create index dm_mu on deleted_messages(mailbox, "
1759 d->t->enqueue( d->q );
1760 d->q = new Query( "create index pn_b on part_numbers(bodypart)",
1762 d->t->enqueue( d->q );
1767 if ( d->substate == 1 ) {
1768 if ( !d->q->done() )
1776 /*! The address_fields table lacks many of the rows it should have had
1777 in revisions prior to 33. This upgrade removes all existing rows,
1778 adds a new column with data we need to keep, parses header_fields
1779 to generate the new rows, and kills the now unnecessary
1782 Well, actually it doesn't do the last step yet. The
1783 MessageHeaderFetcher is careful to disregard these rows, so the do
1787 bool Schema::stepTo32()
1789 if ( d->substate == 0 ) {
1790 describeStep( "Numbering address_fields rows (slow)." );
1792 d->q = new Query( "alter table address_fields add number integer", 0 );
1793 d->t->enqueue( d->q );
1795 d->q = new Query( "set enable_mergejoin to false", 0 );
1796 d->t->enqueue( d->q );
1798 d->q = new Query( "set enable_hashjoin to false", 0 );
1799 d->t->enqueue( d->q );
1802 new Query( "update address_fields set number=0 where "
1803 "(mailbox,uid,part,position,field) in "
1804 "(select mailbox,uid,part,position,field from"
1805 " address_fields group by"
1806 " mailbox,uid,part,position,field"
1807 " having count(*)=1)", 0 );
1808 d->t->enqueue( d->q );
1810 d->q = new Query( "set enable_mergejoin to true", 0 );
1811 d->t->enqueue( d->q );
1813 d->q = new Query( "set enable_hashjoin to true", 0 );
1814 d->t->enqueue( d->q );
1816 EString last( fn( HeaderField::LastAddressField ) );
1817 d->q = new Query( "create index hf_fp on header_fields(field) where "
1818 "field<=" + last + " and part<>''", 0 );
1819 d->t->enqueue( d->q );
1822 new Query( "update address_fields set number=null where "
1823 "(mailbox,uid) in (select distinct mailbox,uid"
1824 " from header_fields where field<=" + last +
1825 " and part<>'')", 0 );
1826 d->t->enqueue( d->q );
1828 EString constraint = "address_fields_mailbox_fkey";
1829 if ( d->version.startsWith( "7" ) )
1833 new Query( "alter table address_fields drop constraint "
1834 "\"" + constraint + "\"", 0 );
1835 d->t->enqueue( d->q );
1838 new Query( "alter table address_fields add constraint "
1839 "address_fields_mailbox_fkey foreign key "
1840 "(mailbox,uid,part) references part_numbers "
1841 "(mailbox,uid,part) on delete cascade", 0 );
1842 d->t->enqueue( d->q );
1845 new Query( "delete from header_fields where field<=" + last +
1846 " and (mailbox,uid) in "
1847 "(select mailbox,uid from address_fields group by"
1848 " mailbox,uid having count(*)=count(number))", 0 );
1849 d->t->enqueue( d->q );
1851 d->q = new Query( "drop index hf_fp", this );
1852 d->t->enqueue( d->q );
1858 if ( d->substate == 1 ) {
1859 if ( !d->q->done() )
1867 /*! Add some indexes to speed up message deletion. */
1869 bool Schema::stepTo33()
1871 if ( d->substate == 0 ) {
1872 describeStep( "Adding indexes to speed up message deletion." );
1873 d->q = new Query( "create index df_mu on date_fields(mailbox,uid)",
1875 d->t->enqueue( d->q );
1876 d->q = new Query( "create index vm_mu on view_messages (source,suid)",
1878 d->t->enqueue( d->q );
1879 d->q = new Query( "create index ms_mu on modsequences(mailbox,uid)",
1881 d->t->enqueue( d->q );
1882 d->q = new Query( "create index dm_mud on deleted_messages"
1883 "(mailbox,uid,deleted_at)", this );
1884 d->t->enqueue( d->q );
1885 d->q = new Query( "drop index dm_mu", this );
1886 d->t->enqueue( d->q );
1891 if ( d->substate == 1 ) {
1892 if ( !d->q->done() )
1900 /*! Add tried_at to deliveries. */
1902 bool Schema::stepTo34()
1904 if ( d->substate == 0 ) {
1905 describeStep( "Adding deliveries.tried_at." );
1906 d->q = new Query( "alter table deliveries add tried_at "
1907 "timestamp with time zone", this );
1908 d->t->enqueue( d->q );
1913 if ( d->substate == 1 ) {
1914 if ( !d->q->done() )
1922 /*! Add sender to deliveries too. */
1924 bool Schema::stepTo35()
1926 if ( d->substate == 0 ) {
1927 describeStep( "Adding deliveries.sender." );
1928 d->q = new Query( "alter table deliveries add sender integer "
1929 "references addresses(id)", this );
1930 d->t->enqueue( d->q );
1931 d->q = new Query( "alter table deliveries alter sender set "
1933 d->t->enqueue( d->q );
1938 if ( d->substate == 1 ) {
1939 if ( !d->q->done() )
1947 /*! Grant "update" on deliveries to aox, because although stepTo28() did
1948 that, schema/grant-privileges did not.
1951 bool Schema::stepTo36()
1953 if ( d->substate == 0 ) {
1954 describeStep( "Granting update on deliveries." );
1955 d->q = new Query( "grant update on deliveries to " + d->dbuser, this );
1956 d->t->enqueue( d->q );
1961 if ( d->substate == 1 ) {
1962 if ( !d->q->done() )
1970 /*! Create the unparsed_messages table if it doesn't exist already.
1971 After this revision, the table exists, but is unfilled; and the
1972 upgraded schema and schema.pg ought to be in sync.
1975 bool Schema::stepTo37()
1977 if ( d->substate == 0 ) {
1978 describeStep( "Creating unparsed_messages if necessary" );
1979 d->q = new Query( "select * from information_schema.tables where "
1980 "table_name='unparsed_messages'", this );
1981 d->t->enqueue( d->q );
1986 if ( d->substate == 1 ) {
1987 if ( !d->q->done() )
1989 if ( !d->q->hasResults() ) {
1991 new Query( "create table unparsed_messages ("
1992 "bodypart integer not null references "
1993 "bodyparts(id) on delete cascade, "
1994 "primary key(bodypart))", this );
1995 d->t->enqueue( d->q );
2004 if ( d->substate == 2 ) {
2005 if ( !d->q->done() )
2013 /*! Grant insert on unparsed_messages. */
2015 bool Schema::stepTo38()
2017 if ( d->substate == 0 ) {
2018 describeStep( "Granting insert on unparsed_messages" );
2019 d->q = new Query( "grant insert on unparsed_messages "
2020 "to " + d->dbuser, this );
2021 d->t->enqueue( d->q );
2026 if ( d->substate == 1 ) {
2027 if ( !d->q->done() )
2035 /*! Add a unique constraint to scripts. */
2037 bool Schema::stepTo39()
2039 if ( d->substate == 0 ) {
2040 describeStep( "Adding unique constraint to scripts" );
2042 new Query( "alter table scripts add constraint "
2043 "scripts_owner_key unique(owner,name)", this );
2044 d->t->enqueue( d->q );
2049 if ( d->substate == 1 ) {
2050 if ( !d->q->done() )
2058 /*! Relax the deleted_messages.deleted_by constraint. */
2060 bool Schema::stepTo40()
2062 if ( d->substate == 0 ) {
2063 describeStep( "Dropping NOT NULL on deleted_messages.deleted_by" );
2065 new Query( "alter table deleted_messages alter deleted_by "
2066 "drop not null", this );
2067 d->t->enqueue( d->q );
2072 if ( d->substate == 1 ) {
2073 if ( !d->q->done() )
2081 /*! Populate unparsed_messages. */
2083 bool Schema::stepTo41()
2085 if ( d->substate == 0 ) {
2086 describeStep( "Populating unparsed_messages" );
2089 "insert into unparsed_messages select distinct p.bodypart "
2090 "from part_numbers p left join deleted_messages dm "
2091 "using (mailbox,uid) left join unparsed_messages um "
2093 "join header_fields hf using (mailbox,uid) "
2094 "where p.part='2' and p.bodypart is not null and "
2095 "dm.uid is null and um.bodypart is null "
2096 "and hf.part='' and hf.field=20 and "
2097 "(hf.value='Message arrived but could not be stored' "
2098 "or hf.value like 'Unparsable message:%')", this
2100 d->t->enqueue( d->q );
2105 if ( d->substate == 1 ) {
2106 if ( !d->q->done() )
2114 /*! Insert modsequences for any messages that don't have them. */
2116 bool Schema::stepTo42()
2118 if ( d->substate == 0 ) {
2119 describeStep( "Populating modsequences for old messages" );
2122 "insert into modsequences (mailbox,uid,modseq) "
2123 "select mailbox,uid,(select nextval('nextmodsequence')) "
2124 "from messages m left join modsequences ms "
2125 "using (mailbox,uid) where ms.uid is null", this
2127 d->t->enqueue( d->q );
2132 if ( d->substate == 1 ) {
2133 if ( !d->q->done() )
2141 /*! Make nextmodseq be per-mailbox. */
2143 bool Schema::stepTo43()
2145 if ( d->substate == 0 ) {
2146 describeStep( "Assigning nextmodseq for each mailbox" );
2147 d->q = new Query( "alter table mailboxes add nextmodseq bigint", this );
2148 d->t->enqueue( d->q );
2149 d->q = new Query( "update mailboxes set nextmodseq="
2150 "(select nextval('nextmodsequence'))", this );
2151 d->t->enqueue( d->q );
2152 d->q = new Query( "alter table mailboxes alter nextmodseq "
2153 "set not null", this );
2154 d->t->enqueue( d->q );
2155 d->q = new Query( "alter table mailboxes alter nextmodseq "
2156 "set default 1", this );
2157 d->t->enqueue( d->q );
2158 d->q = new Query( "drop sequence nextmodsequence", this );
2159 d->t->enqueue( d->q );
2164 if ( d->substate == 1 ) {
2165 if ( !d->q->done() )
2173 /*! Add some primary keys (the easy ones). */
2175 bool Schema::stepTo44()
2177 if ( d->substate == 0 ) {
2178 describeStep( "Adding primary keys to some tables" );
2179 d->q = new Query( "alter table annotations add primary key "
2180 "(mailbox,uid,owner,name)", this );
2181 d->t->enqueue( d->q );
2182 d->q = new Query( "alter table modsequences add primary key "
2183 "(mailbox,uid)", this );
2184 d->t->enqueue( d->q );
2185 d->q = new Query( "drop index ms_mu", this );
2186 d->t->enqueue( d->q );
2187 d->q = new Query( "alter table permissions add primary key "
2188 "(mailbox,identifier)", this );
2189 d->t->enqueue( d->q );
2190 d->q = new Query( "alter table group_members add primary key "
2191 "(groupname,member)", this );
2192 d->t->enqueue( d->q );
2193 d->q = new Query( "alter table thread_members add primary key "
2194 "(thread,mailbox,uid)", this );
2195 d->t->enqueue( d->q );
2196 d->q = new Query( "alter table mailstore add primary key "
2197 "(revision)", this );
2198 d->t->enqueue( d->q );
2203 if ( d->substate == 1 ) {
2204 if ( !d->q->done() )
2212 /*! Add an index on users.login. */
2214 bool Schema::stepTo45()
2216 if ( d->substate == 0 ) {
2217 describeStep( "Adding an index on users.login" );
2218 d->q = new Query( "create index u_l on users(lower(login))", this );
2219 d->t->enqueue( d->q );
2224 if ( d->substate == 1 ) {
2225 if ( !d->q->done() )
2233 /*! Remove duplicates from deleted_messages, and add a primary key. */
2235 bool Schema::stepTo46()
2237 if ( d->substate == 0 ) {
2238 describeStep( "Adding a primary key to deleted_messages" );
2239 d->q = new Query( "create aggregate array_accum "
2240 "(basetype=anyelement, sfunc=array_append,"
2241 " stype=anyarray, initcond='{}')", this );
2242 d->t->enqueue( d->q );
2243 d->q = new Query( "delete from deleted_messages where ctid in "
2244 "(select d.ctid from deleted_messages d join "
2245 "(select mailbox,uid,array_accum(ctid) as tids "
2246 "from deleted_messages group by mailbox,uid "
2247 "having count(*)>1) ds using (mailbox,uid) where "
2248 "not (d.ctid=tids[1]))", this );
2249 d->t->enqueue( d->q );
2250 d->q = new Query( "drop aggregate array_accum (anyelement)", this );
2251 d->t->enqueue( d->q );
2252 d->q = new Query( "alter table deleted_messages add "
2253 "primary key (mailbox,uid)", this );
2254 d->t->enqueue( d->q );
2259 if ( d->substate == 1 ) {
2260 if ( !d->q->done() )
2268 /*! Unconstrain annotations.owner and add a surrogate key. */
2270 bool Schema::stepTo47()
2272 if ( d->substate == 0 ) {
2273 describeStep( "Adding a surrogate key to annotations" );
2274 d->q = new Query( "alter table annotations drop constraint "
2275 "annotations_pkey", this );
2276 d->t->enqueue( d->q );
2277 d->q = new Query( "alter table annotations alter owner "
2278 "drop not null", this );
2279 d->t->enqueue( d->q );
2280 d->q = new Query( "alter table annotations add id serial", this );
2281 d->t->enqueue( d->q );
2282 d->q = new Query( "update annotations set id=nextval("
2283 "pg_get_serial_sequence('annotations','id'))",
2285 d->t->enqueue( d->q );
2286 d->q = new Query( "alter table annotations alter id "
2287 "set not null", this );
2288 d->t->enqueue( d->q );
2289 d->q = new Query( "alter table annotations add "
2290 "unique (mailbox,uid,owner,name)", this );
2291 d->t->enqueue( d->q );
2292 d->q = new Query( "alter table annotations add "
2293 "primary key (id)", this );
2294 d->t->enqueue( d->q );
2299 if ( d->substate == 1 ) {
2300 if ( !d->q->done() )
2308 /*! Grant select,update on annotations_id_seq. */
2310 bool Schema::stepTo48()
2312 if ( d->substate == 0 ) {
2313 describeStep( "Granting privileges on annotations_id_seq" );
2314 d->q = new Query( "grant select,update on annotations_id_seq "
2315 "to " + d->dbuser, this );
2316 d->t->enqueue( d->q );
2321 if ( d->substate == 1 ) {
2322 if ( !d->q->done() )
2330 /*! Grant privileges on threads and thread_members. */
2332 bool Schema::stepTo49()
2334 if ( d->substate == 0 ) {
2335 describeStep( "Granting privileges on thread*" );
2336 d->q = new Query( "grant select,insert on threads "
2337 "to " + d->dbuser, this );
2338 d->t->enqueue( d->q );
2339 d->q = new Query( "grant select,insert on thread_members "
2340 "to " + d->dbuser, this );
2341 d->t->enqueue( d->q );
2342 d->q = new Query( "grant select,update on threads_id_seq "
2343 "to " + d->dbuser, this );
2344 d->t->enqueue( d->q );
2349 if ( d->substate == 1 ) {
2350 if ( !d->q->done() )
2358 /*! Add deliveries.delivered_at. */
2360 bool Schema::stepTo50()
2362 if ( d->substate == 0 ) {
2363 describeStep( "Adding deliveries.delivered_at" );
2364 d->q = new Query( "alter table deliveries add delivered_at "
2365 "timestamp with time zone", this );
2366 d->t->enqueue( d->q );
2371 if ( d->substate == 1 ) {
2372 if ( !d->q->done() )
2380 /*! Split delivery_recipients away from deliveries. */
2382 bool Schema::stepTo51()
2384 if ( d->substate == 0 ) {
2385 describeStep( "Creating delivery_recipients" );
2386 d->q = new Query( "create table delivery_recipients ("
2387 "id serial primary key, delivery integer "
2388 "not null references deliveries(id) on delete "
2389 "cascade, recipient integer not null references "
2390 "addresses(id), status text)", this );
2391 d->t->enqueue( d->q );
2392 d->q = new Query( "grant select, insert, update on "
2393 "delivery_recipients to " + d->dbuser, this );
2394 d->t->enqueue( d->q );
2395 d->q = new Query( "alter table deliveries drop recipient", this );
2396 d->t->enqueue( d->q );
2397 describeStep( "Emptying the spool" );
2398 d->q = new Query( "delete from deliveries", this );
2399 d->t->enqueue( d->q );
2401 new Query( "insert into deleted_messages (mailbox,uid,reason) "
2402 "select mailbox,uid,'spool emptied' from messages "
2403 "join mailboxes on (mailbox=id) where "
2404 "name='/archiveopteryx/spool'", this );
2405 d->t->enqueue( d->q );
2410 if ( d->substate == 1 ) {
2411 if ( !d->q->done() )
2419 /*! Add delivery_recipients.action and last_attempt. */
2421 bool Schema::stepTo52()
2423 if ( d->substate == 0 ) {
2424 describeStep( "Adding delivery_recipients.action/last_attempt" );
2425 d->q = new Query( "alter table deliveries drop delivered_at", this );
2426 d->t->enqueue( d->q );
2427 d->q = new Query( "alter table delivery_recipients add "
2428 "last_attempt timestamp with time zone", this );
2429 d->t->enqueue( d->q );
2430 d->q = new Query( "alter table delivery_recipients add "
2431 "action integer not null default 0", this );
2432 d->t->enqueue( d->q );
2437 if ( d->substate == 1 ) {
2438 if ( !d->q->done() )
2446 /*! We need permissions on the delivery_recipients sequence too. */
2448 bool Schema::stepTo53()
2450 if ( d->substate == 0 ) {
2451 describeStep( "Granting privileges on delivery_recipients_id_seq" );
2452 d->q = new Query( "grant select, update on "
2453 "delivery_recipients_id_seq to " + d->dbuser, this );
2454 d->t->enqueue( d->q );
2459 if ( d->substate == 1 ) {
2460 if ( !d->q->done() )
2468 /*! Make (mailbox,uid) unique in deliveries. */
2470 bool Schema::stepTo54()
2472 if ( d->substate == 0 ) {
2473 describeStep( "Making (mailbox,uid) unique in deliveries" );
2474 d->q = new Query( "alter table deliveries add unique(mailbox,uid)",
2476 d->t->enqueue( d->q );
2481 if ( d->substate == 1 ) {
2482 if ( !d->q->done() )
2490 /*! Convert mUTF-7 mailbox names to UTF-8. */
2492 bool Schema::stepTo55()
2494 if ( d->substate == 0 ) {
2495 describeStep( "Converting mUTF-7 mailbox names to UTF-8" );
2496 d->q = new Query( "select id, name from mailboxes where "
2497 "name like '%&%'", this );
2498 d->t->enqueue( d->q );
2504 if ( d->substate == 1 ) {
2505 while ( d->q->hasResults() ) {
2506 Row * r = d->q->nextRow();
2511 EString oldName( r->getEString( "name" ) );
2512 EString newName( u.fromUnicode( mu.toUnicode( oldName ) ) );
2514 if ( mu.wellformed() && oldName != newName ) {
2516 new Query( "update mailboxes set name=$1 "
2517 "where id=$2", this );
2519 q->bind( 1, newName );
2520 q->bind( 2, r->getInt( "id" ) );
2526 if ( !d->q->done() )
2532 if ( d->substate == 2 ) {
2533 if ( d->update && !d->update->done() )
2541 /*! Create the vacation_responses table. */
2543 bool Schema::stepTo56()
2545 if ( d->substate == 0 ) {
2546 describeStep( "Creating vacation_responses table." );
2547 d->q = new Query( "create table vacation_responses (id serial "
2548 "primary key,sent_from integer not null references "
2549 "addresses(id),sent_to integer not null references "
2550 "addresses(id),expires_at timestamp with time zone "
2551 "default current_timestamp+interval '7 days',"
2552 "handle text)", this );
2553 d->t->enqueue( d->q );
2554 d->q = new Query( "grant select,insert on vacation_responses "
2555 "to " + d->dbuser, this );
2556 d->t->enqueue( d->q );
2557 d->q = new Query( "grant select,update on vacation_responses_id_seq "
2558 "to " + d->dbuser, this );
2559 d->t->enqueue( d->q );
2564 if ( d->substate == 1 ) {
2565 if ( !d->q->done() )
2573 /*! Rename vacation_responses to autoresponses. (We do this by dropping
2574 the old table and creating a new one, so that the sequence is also
2578 bool Schema::stepTo57()
2580 if ( d->substate == 0 ) {
2581 describeStep( "Renaming vacation_responses to autoresponses." );
2582 d->q = new Query( "drop table vacation_responses", this );
2583 d->t->enqueue( d->q );
2584 d->q = new Query( "create table autoresponses (id serial "
2585 "primary key,sent_from integer not null references "
2586 "addresses(id),sent_to integer not null references "
2587 "addresses(id),expires_at timestamp with time zone "
2588 "default current_timestamp+interval '7 days',"
2589 "handle text)", this );
2590 d->t->enqueue( d->q );
2591 d->q = new Query( "grant select,insert on autoresponses "
2592 "to " + d->dbuser, this );
2593 d->t->enqueue( d->q );
2594 d->q = new Query( "grant select,update on autoresponses_id_seq "
2595 "to " + d->dbuser, this );
2596 d->t->enqueue( d->q );
2601 if ( d->substate == 1 ) {
2602 if ( !d->q->done() )
2610 /*! Add a missing "on delete cascade" clause to scripts. */
2612 bool Schema::stepTo58()
2614 if ( d->substate == 0 ) {
2615 describeStep( "Adding missing 'on delete cascade' to scripts." );
2616 d->q = new Query( "alter table scripts drop constraint "
2617 "\"scripts_owner_fkey\"", this );
2618 d->t->enqueue( d->q );
2619 d->q = new Query( "alter table scripts add constraint "
2620 "scripts_owner_fkey foreign key (owner) "
2621 "references users(id) on delete cascade",
2623 d->t->enqueue( d->q );
2628 if ( d->substate == 1 ) {
2629 if ( !d->q->done() )
2637 /*! Delete duplicate addresses: By mistake the unique index used a
2638 case-sensitive domain. We keep the oldest version seen.
2641 bool Schema::stepTo59()
2643 if ( d->substate == 0 ) {
2644 describeStep( "Deleting duplicate addresses." );
2646 "select a.localpart, a.domain, b.domain as domain2, "
2647 "a.id as original, b.id as duplicate "
2648 "from addresses a, addresses b "
2649 "where a.id<b.id and a.name=b.name "
2650 "and a.localpart=b.localpart "
2651 "and lower(a.domain)=lower(b.domain)", this );
2652 d->t->enqueue( d->q );
2657 if ( d->substate == 1 ) {
2658 if ( !d->q->done() )
2661 PreparedStatement af ( "update address_fields "
2662 "set address=$1 where address=$2" );
2663 PreparedStatement aliases( "update aliases "
2664 "set address=$1 where address=$2" );
2665 PreparedStatement deliveries( "update deliveries "
2666 "set sender=$1 where sender=$2" );
2667 PreparedStatement dr( "update delivery_recipients "
2668 "set recipient=$1 where recipient=$2" );
2669 PreparedStatement arf( "update autoresponses "
2670 "set sent_from=$1 where sent_from=$2" );
2671 PreparedStatement art( "update autoresponses "
2672 "set sent_to=$1 where sent_to=$2" );
2676 Row * r = d->q->nextRow();
2678 uint original = r->getInt( "original" );
2679 uint duplicate = r->getInt( "duplicate" );
2680 d->l->log( "Changing " +
2681 r->getEString( "localpart" ) + "@" +
2682 r->getEString( "domain2" ) + " to " +
2683 r->getEString( "localpart" ) + "@" +
2684 r->getEString( "domain" ) + "@" );
2685 q = new Query( af, 0 );
2686 q->bind( 1, original );
2687 q->bind( 1, duplicate );
2689 q = new Query( aliases, 0 );
2690 q->bind( 1, original );
2691 q->bind( 1, duplicate );
2693 q = new Query( deliveries, 0 );
2694 q->bind( 1, original );
2695 q->bind( 1, duplicate );
2697 q = new Query( dr, 0 );
2698 q->bind( 1, original );
2699 q->bind( 1, duplicate );
2701 q = new Query( arf, 0 );
2702 q->bind( 1, original );
2703 q->bind( 1, duplicate );
2705 q = new Query( art, 0 );
2706 q->bind( 1, original );
2707 q->bind( 1, duplicate );
2709 if ( dfa.isEmpty() )
2710 dfa = "delete from addresses where id=";
2712 dfa.append( " or id=" );
2713 dfa.appendNumber( duplicate );
2714 r = d->q->nextRow();
2717 q = new Query( dfa, 0 );
2720 q = new Query( "alter table addresses drop constraint "
2721 "addresses_name_key", 0 );
2723 q = new Query( "create unique index addresses_nld_key "
2724 "on addresses(name,localpart,lower(domain))",
2731 if ( d->substate == 2 ) {
2732 if ( !d->q->done() )
2740 /*! Split messages into two, and clean up the resulting mess. */
2742 bool Schema::stepTo60()
2744 if ( d->substate == 0 ) {
2745 describeStep( "Splitting the messages table (may be very slow)." );
2747 // First, we'll add messages.id and make it a candidate key so
2748 // that we can refer to it from other tables. Then we'll create
2749 // the new mailbox_messages table.
2751 describeStep( "1. Separating messages and mailbox_messages" );
2753 d->q = new Query( "alter table messages add id serial", this );
2754 d->t->enqueue( d->q );
2755 d->q = new Query( "alter table messages alter id set not null", this );
2756 d->t->enqueue( d->q );
2757 d->q = new Query( "alter table messages add unique(id)", this );
2758 d->t->enqueue( d->q );
2760 d->q = new Query( "create table mailbox_messages (mailbox integer not "
2761 "null references mailboxes(id),uid integer not null,"
2762 "message integer not null references messages(id),"
2763 "idate integer not null,modseq bigint not null,"
2764 "primary key(mailbox,uid))", this );
2765 d->t->enqueue( d->q );
2767 d->q = new Query( "grant select,insert,update on mailbox_messages "
2768 "to " + d->dbuser, this );
2769 d->t->enqueue( d->q );
2771 d->q = new Query( "insert into mailbox_messages "
2772 "(mailbox,uid,message,idate,modseq) "
2773 "select mailbox,uid,messages.id,idate,modseq from "
2774 "messages join modsequences using (mailbox,uid) "
2775 "left join deleted_messages using (mailbox,uid) "
2776 "where deleted_messages is null", this );
2777 d->t->enqueue( d->q );
2779 d->q = new Query( "alter table messages drop idate", this );
2780 d->t->enqueue( d->q );
2782 // Fetch the names of all foreign key references to messages.
2784 d->q = new Query( "select d.relname::text,c.conname::text,"
2785 "pg_get_constraintdef(c.oid) as condef "
2786 "from pg_constraint c join pg_class d "
2787 "on (c.conrelid=d.oid) join pg_class e "
2788 "on (c.confrelid=e.oid) where c.contype='f' "
2789 "and e.relname='messages'", this );
2790 d->t->enqueue( d->q );
2796 if ( d->substate == 1 ) {
2797 if ( !d->q->done() )
2800 describeStep( "2. Updating foreign key references" );
2802 if ( d->q->failed() || d->q->rows() == 0 ) {
2803 fail( "Couldn't fetch references to messages", d->q );
2807 Dict<EString> constraints;
2809 while ( d->q->hasResults() ) {
2810 Row * r = d->q->nextRow();
2812 r->getEString( "relname" ),
2813 new EString( r->getEString( "conname" ) )
2817 d->q = new Query( "alter table part_numbers drop constraint " +
2818 constraints.find( "part_numbers" )->quoted(),
2820 d->t->enqueue( d->q );
2822 d->q = new Query( "alter table date_fields drop constraint " +
2823 constraints.find( "date_fields" )->quoted(),
2825 d->t->enqueue( d->q );
2827 d->q = new Query( "alter table thread_members drop constraint " +
2828 constraints.find( "thread_members" )->quoted(),
2830 d->t->enqueue( d->q );
2832 d->q = new Query( "alter table flags drop constraint " +
2833 constraints.find( "flags" )->quoted(), this );
2834 d->t->enqueue( d->q );
2836 d->q = new Query( "delete from flags using deleted_messages d "
2837 "where flags.mailbox=d.mailbox and "
2838 "flags.uid=d.uid", this );
2839 d->t->enqueue( d->q );
2841 d->q = new Query( "alter table flags add constraint "
2842 "flags_mailbox_fkey foreign key "
2843 "(mailbox,uid) references "
2844 "mailbox_messages (mailbox,uid) "
2845 "on delete cascade", this );
2846 d->t->enqueue( d->q );
2848 d->q = new Query( "alter table annotations drop constraint " +
2849 constraints.find( "annotations" )->quoted(),
2851 d->t->enqueue( d->q );
2853 d->q = new Query( "delete from annotations using "
2854 "deleted_messages d where "
2855 "annotations.mailbox=d.mailbox and "
2856 "annotations.uid=d.uid",
2858 d->t->enqueue( d->q );
2860 d->q = new Query( "alter table annotations add constraint "
2861 "annotations_mailbox_fkey foreign key "
2862 "(mailbox,uid) references "
2863 "mailbox_messages (mailbox,uid) "
2864 "on delete cascade", this );
2865 d->t->enqueue( d->q );
2867 d->q = new Query( "alter table view_messages drop constraint " +
2868 constraints.find( "view_messages" )->quoted(),
2870 d->t->enqueue( d->q );
2872 d->q = new Query( "alter table deleted_messages drop constraint " +
2873 constraints.find( "deleted_messages" )->quoted(),
2875 d->t->enqueue( d->q );
2877 d->q = new Query( "alter table modsequences drop constraint " +
2878 constraints.find( "modsequences" )->quoted(),
2880 d->t->enqueue( d->q );
2882 d->q = new Query( "alter table deliveries drop constraint " +
2883 constraints.find( "deliveries" )->quoted(),
2885 d->t->enqueue( d->q );
2887 d->q = new Query( "select d.relname::text,c.conname::text,"
2888 "pg_get_constraintdef(c.oid) as condef "
2889 "from pg_constraint c join pg_class d "
2890 "on (c.conrelid=d.oid) join pg_class e "
2891 "on (c.confrelid=e.oid) where c.contype='f' "
2892 "and e.relname='part_numbers'", this );
2893 d->t->enqueue( d->q );
2900 if ( d->substate == 2 ) {
2901 if ( !d->q->done() )
2904 describeStep( "3. Updating part_numbers and "
2905 "header/address/date_fields" );
2907 if ( d->q->failed() || d->q->rows() == 0 ) {
2908 fail( "Couldn't fetch references to part_numbers", d->q );
2912 Dict<EString> constraints;
2914 while ( d->q->hasResults() ) {
2915 Row * r = d->q->nextRow();
2917 r->getEString( "relname" ),
2918 new EString( r->getEString( "conname" ) )
2922 d->q = new Query( "alter table header_fields drop constraint " +
2923 constraints.find( "header_fields" )->quoted(),
2925 d->t->enqueue( d->q );
2927 d->q = new Query( "alter table address_fields drop constraint " +
2928 constraints.find( "address_fields" )->quoted(),
2930 d->t->enqueue( d->q );
2932 d->q = new Query( "alter table part_numbers add message "
2934 d->t->enqueue( d->q );
2936 d->q = new Query( "update part_numbers set message=m.id "
2937 "from messages m where "
2938 "part_numbers.mailbox=m.mailbox and "
2939 "part_numbers.uid=m.uid", this );
2940 d->t->enqueue( d->q );
2942 d->q = new Query( "alter table part_numbers alter message "
2943 "set not null", this );
2944 d->t->enqueue( d->q );
2946 d->q = new Query( "alter table part_numbers add constraint "
2947 "part_numbers_message_fkey foreign key "
2948 "(message) references messages(id) "
2949 "on delete cascade", this );
2950 d->t->enqueue( d->q );
2952 d->q = new Query( "select d.relname::text,c.conname::text,"
2953 "pg_get_constraintdef(c.oid) as condef "
2954 "from pg_constraint c join pg_class d "
2955 "on (c.conrelid=d.oid) where c.contype='p' "
2956 "and d.relname='part_numbers'", this );
2957 d->t->enqueue( d->q );
2964 if ( d->substate == 3 ) {
2965 if ( !d->q->done() )
2968 if ( d->q->failed() || d->q->rows() != 1 ) {
2969 fail( "Couldn't fetch primary key for part_numbers", d->q );
2973 Row * r = d->q->nextRow();
2975 d->q = new Query( "alter table part_numbers drop constraint " +
2976 r->getEString( "conname" ).quoted(), this );
2977 d->t->enqueue( d->q );
2978 d->q = new Query( "alter table part_numbers add constraint "
2979 "part_numbers_pkey primary key (message,part)",
2981 d->t->enqueue( d->q );
2983 d->q = new Query( "select d.relname::text,c.conname::text,"
2984 "pg_get_constraintdef(c.oid) as condef "
2985 "from pg_constraint c join pg_class d "
2986 "on (c.conrelid=d.oid) where c.contype='u' "
2987 "and d.relname='header_fields'", this );
2988 d->t->enqueue( d->q );
2995 if ( d->substate == 4 ) {
2996 if ( !d->q->done() )
2999 if ( d->q->failed() || d->q->rows() == 0 ) {
3000 fail( "Couldn't fetch unique constraint on header_fields", d->q );
3004 Dict<EString> constraints;
3006 while ( d->q->hasResults() ) {
3007 Row * r = d->q->nextRow();
3009 r->getEString( "relname" ),
3010 new EString( r->getEString( "conname" ) )
3014 d->q = new Query( "alter table header_fields drop constraint " +
3015 constraints.find( "header_fields" )->quoted(),
3017 d->t->enqueue( d->q );
3019 d->q = new Query( "alter table header_fields add message "
3021 d->t->enqueue( d->q );
3022 d->q = new Query( "alter table address_fields add message "
3024 d->t->enqueue( d->q );
3025 d->q = new Query( "alter table date_fields add message "
3027 d->t->enqueue( d->q );
3029 d->q = new Query( "update header_fields set message=m.id "
3030 "from messages m where "
3031 "header_fields.mailbox=m.mailbox and "
3032 "header_fields.uid=m.uid", this );
3033 d->t->enqueue( d->q );
3034 d->q = new Query( "update address_fields set message=m.id "
3035 "from messages m where "
3036 "address_fields.mailbox=m.mailbox and "
3037 "address_fields.uid=m.uid", this );
3038 d->t->enqueue( d->q );
3039 d->q = new Query( "update date_fields set message=m.id from "
3041 "date_fields.mailbox=m.mailbox and "
3042 "date_fields.uid=m.uid", this );
3043 d->t->enqueue( d->q );
3045 d->q = new Query( "alter table header_fields alter message "
3046 "set not null", this );
3047 d->t->enqueue( d->q );
3048 d->q = new Query( "alter table address_fields alter message "
3049 "set not null", this );
3050 d->t->enqueue( d->q );
3051 d->q = new Query( "alter table date_fields alter message "
3052 "set not null", this );
3053 d->t->enqueue( d->q );
3055 d->q = new Query( "drop index hf_mup", this );
3056 d->t->enqueue( d->q );
3057 d->q = new Query( "drop index af_mu", this );
3058 d->t->enqueue( d->q );
3059 d->q = new Query( "drop index df_mu", this );
3060 d->t->enqueue( d->q );
3062 d->q = new Query( "alter table header_fields drop mailbox", this );
3063 d->t->enqueue( d->q );
3064 d->q = new Query( "alter table address_fields drop mailbox", this );
3065 d->t->enqueue( d->q );
3066 d->q = new Query( "alter table date_fields drop mailbox", this );
3067 d->t->enqueue( d->q );
3069 d->q = new Query( "alter table header_fields drop uid", this );
3070 d->t->enqueue( d->q );
3071 d->q = new Query( "alter table address_fields drop uid", this );
3072 d->t->enqueue( d->q );
3073 d->q = new Query( "alter table date_fields drop uid", this );
3074 d->t->enqueue( d->q );
3076 d->q = new Query( "alter table header_fields add constraint "
3077 "header_fields_message_fkey foreign key "
3078 "(message,part) references "
3079 "part_numbers(message, part) "
3080 "on delete cascade", this );
3081 d->t->enqueue( d->q );
3082 d->q = new Query( "alter table address_fields add constraint "
3083 "address_fields_message_fkey foreign key "
3084 "(message,part) references "
3085 "part_numbers(message, part) "
3086 "on delete cascade", this );
3087 d->t->enqueue( d->q );
3088 d->q = new Query( "alter table date_fields add constraint "
3089 "date_fields_message_fkey foreign key (message) "
3090 "references messages(id) on delete cascade",
3092 d->t->enqueue( d->q );
3094 d->q = new Query( "alter table header_fields add constraint "
3095 "header_fields_message_key "
3096 "unique(message,part,position,field)", this );
3097 d->t->enqueue( d->q );
3099 d->q = new Query( "create index af_mp on address_fields "
3100 "(message,part)", this );
3101 d->t->enqueue( d->q );
3103 d->q = new Query( "alter table part_numbers drop mailbox", this );
3104 d->t->enqueue( d->q );
3105 d->q = new Query( "alter table part_numbers drop uid", this );
3106 d->t->enqueue( d->q );
3113 if ( d->substate == 5 ) {
3114 if ( !d->q->done() )
3117 describeStep( "4. Updating deliveries" );
3119 d->q = new Query( "alter table deliveries add message "
3121 d->t->enqueue( d->q );
3123 d->q = new Query( "update deliveries set message=m.id "
3124 "from messages m where "
3125 "deliveries.mailbox=m.mailbox and "
3126 "deliveries.uid=m.uid", this );
3127 d->t->enqueue( d->q );
3129 d->q = new Query( "alter table deliveries alter message "
3130 "set not null", this );
3131 d->t->enqueue( d->q );
3133 d->q = new Query( "alter table deliveries drop mailbox", this );
3134 d->t->enqueue( d->q );
3136 d->q = new Query( "alter table deliveries drop uid", this );
3137 d->t->enqueue( d->q );
3139 d->q = new Query( "alter table deliveries add constraint "
3140 "deliveries_message_fkey foreign key "
3141 "(message) references messages(id) "
3142 "on delete cascade", this );
3143 d->t->enqueue( d->q );
3144 d->q = new Query( "alter table deliveries add constraint "
3145 "deliveries_message_key unique(message)",
3147 d->t->enqueue( d->q );
3153 if ( d->substate == 6 ) {
3154 if ( !d->q->done() )
3157 describeStep( "5. Updating deleted_messages" );
3159 d->q = new Query( "alter table deleted_messages add message "
3161 d->t->enqueue( d->q );
3163 d->q = new Query( "update deleted_messages set message=m.id "
3164 "from messages m where "
3165 "deleted_messages.mailbox=m.mailbox and "
3166 "deleted_messages.uid=m.uid", this );
3167 d->t->enqueue( d->q );
3169 d->q = new Query( "alter table deleted_messages alter message "
3170 "set not null", this );
3171 d->t->enqueue( d->q );
3173 d->q = new Query( "alter table deleted_messages add constraint "
3174 "deleted_messages_mailbox_fkey foreign key "
3175 "(mailbox) references mailboxes(id)", this );
3176 d->t->enqueue( d->q );
3177 d->q = new Query( "alter table deleted_messages add constraint "
3178 "deleted_messages_message_fkey foreign key "
3179 "(message) references messages(id)", this );
3180 d->t->enqueue( d->q );
3186 if ( d->substate == 7 ) {
3187 if ( !d->q->done() )
3190 describeStep( "6. Dropping unnecessary tables and columns" );
3192 d->q = new Query( "alter table messages drop mailbox", this );
3193 d->t->enqueue( d->q );
3194 d->q = new Query( "alter table messages drop uid", this );
3195 d->t->enqueue( d->q );
3197 d->q = new Query( "drop table modsequences", this );
3198 d->t->enqueue( d->q );
3199 d->q = new Query( "drop table view_messages", this );
3200 d->t->enqueue( d->q );
3202 d->q = new Query( "select d.relname::text,c.conname::text,"
3203 "pg_get_constraintdef(c.oid) as condef "
3204 "from pg_constraint c join pg_class d "
3205 "on (c.conrelid=d.oid) where c.contype='u' "
3206 "and d.relname='users'", this );
3207 d->t->enqueue( d->q );
3213 if ( d->substate == 8 ) {
3214 if ( !d->q->done() )
3217 describeStep( "7. Miscellaneous changes" );
3219 if ( d->q->failed() || d->q->rows() == 0 ) {
3220 fail( "Couldn't fetch unique constraint on users", d->q );
3224 Row * r = d->q->nextRow();
3226 d->q = new Query( "alter table users drop constraint " +
3227 r->getEString( "conname" ).quoted(), this );
3228 d->t->enqueue( d->q );
3229 d->q = new Query( "drop index u_l", this );
3230 d->t->enqueue( d->q );
3231 d->q = new Query( "create unique index u_l on users "
3232 "(lower(login))", this );
3233 d->t->enqueue( d->q );
3240 if ( d->substate == 9 ) {
3241 if ( !d->q->done() )
3247 if ( d->substate == 42 ) {
3248 if ( !d->q->done() )
3256 /*! Grant select,update on messages_id_seq. */
3258 bool Schema::stepTo61()
3260 if ( d->substate == 0 ) {
3261 describeStep( "Granting privileges on messages_id_seq" );
3262 d->q = new Query( "grant select,update on messages_id_seq "
3263 "to " + d->dbuser, this );
3264 d->t->enqueue( d->q );
3269 if ( d->substate == 1 ) {
3270 if ( !d->q->done() )
3278 /*! Create a trigger on deleted_messages to remove the message. */
3280 bool Schema::stepTo62()
3282 if ( d->substate == 0 ) {
3283 describeStep( "Adding deleted_messages_trigger." );
3284 d->q = new Query( "select lanname::text from pg_catalog.pg_language "
3285 "where lanname='plpgsql'", this );
3286 d->t->enqueue( d->q );
3291 if ( d->substate == 1 ) {
3292 if ( !d->q->done() )
3295 if ( d->q->failed() || !d->q->hasResults() ) {
3296 fail( "PL/PgSQL is not available. Please re-run the "
3297 "Archiveopteryx installer to enable PL/PgSQL." );
3302 new Query( "create function delete_message() "
3303 "returns trigger as $$"
3304 "begin delete from mailbox_messages where "
3305 "mailbox=NEW.mailbox and uid=NEW.uid; return NULL; "
3306 "end;$$ language plpgsql security definer", this );
3307 d->t->enqueue( d->q );
3309 new Query( "create trigger deleted_messages_trigger "
3310 "after insert on deleted_messages for each "
3311 "row execute procedure delete_message()", this );
3312 d->t->enqueue( d->q );
3318 if ( d->substate == 2 ) {
3319 if ( !d->q->done() )
3327 /*! Add deleted_messages.modseq. */
3329 bool Schema::stepTo63()
3331 if ( d->substate == 0 ) {
3332 describeStep( "Adding deleted_messages.modseq" );
3333 d->q = new Query( "alter table deleted_messages add "
3334 "modseq bigint", this );
3335 d->t->enqueue( d->q );
3336 d->q = new Query( "update deleted_messages set modseq=nextmodseq-1 "
3337 "from mailboxes m where "
3338 "deleted_messages.mailbox=m.id", this );
3339 d->t->enqueue( d->q );
3340 d->q = new Query( "alter table deleted_messages alter modseq "
3341 "set not null", this );
3342 d->t->enqueue( d->q );
3347 if ( d->substate == 1 ) {
3348 if ( !d->q->done() )
3356 /*! Make deleted_messages.message cascade on delete. */
3358 bool Schema::stepTo64()
3360 if ( d->substate == 0 ) {
3361 describeStep( "Altering deleted_messages_message_fkey." );
3362 d->q = new Query( "alter table deleted_messages drop constraint "
3363 "deleted_messages_message_fkey", this );
3364 d->t->enqueue( d->q );
3365 d->q = new Query( "alter table deleted_messages add constraint "
3366 "deleted_messages_message_fkey foreign key "
3367 "(message) references messages(id) "
3368 "on delete cascade", this );
3369 d->t->enqueue( d->q );
3374 if ( d->substate == 1 ) {
3375 if ( !d->q->done() )
3383 /*! Grant "update" on threads to aox, so that the threader can lock the
3384 table in exclusive mode.
3387 bool Schema::stepTo65()
3389 if ( d->substate == 0 ) {
3390 describeStep( "Granting update on threads." );
3391 d->q = new Query( "grant update on threads to " + d->dbuser, this );
3392 d->t->enqueue( d->q );
3397 if ( d->substate == 1 ) {
3398 if ( !d->q->done() )
3406 /*! Change the unique constraint on threads to include "mailbox". */
3408 bool Schema::stepTo66()
3410 if ( d->substate == 0 ) {
3411 describeStep( "Changing unique constraint on threads." );
3412 d->q = new Query( "select d.relname::text,c.conname::text,"
3413 "pg_get_constraintdef(c.oid) as condef "
3414 "from pg_constraint c join pg_class d "
3415 "on (c.conrelid=d.oid) where c.contype='u' "
3416 "and d.relname='threads'", this );
3417 d->t->enqueue( d->q );
3422 if ( d->substate == 1 ) {
3423 if ( !d->q->done() )
3426 if ( d->q->failed() || d->q->rows() == 0 ) {
3427 fail( "Couldn't fetch unique constraint on threads", d->q );
3431 Row * r = d->q->nextRow();
3432 d->q = new Query( "alter table threads drop constraint " +
3433 r->getEString( "conname" ).quoted(), this );
3434 d->t->enqueue( d->q );
3435 d->q = new Query( "alter table threads add constraint "
3436 "threads_subject_key unique "
3437 "(mailbox,subject)", this );
3438 d->t->enqueue( d->q );
3444 if ( d->substate == 2 ) {
3445 if ( !d->q->done() )
3453 /*! Create a couple of new indexes to make "aox vacuum" faster, and help
3454 to look for specific message-ids.
3457 bool Schema::stepTo67()
3459 if ( d->substate == 0 ) {
3460 describeStep( "Creating indexes to help foreign key/msgid lookups." );
3461 d->q = new Query( "create index mm_m on mailbox_messages(message)", this );
3462 d->t->enqueue( d->q );
3463 d->q = new Query( "create index dm_m on deleted_messages(message)", this );
3464 d->t->enqueue( d->q );
3465 d->q = new Query( "create index df_m on date_fields(message)", this );
3466 d->t->enqueue( d->q );
3467 d->q = new Query( "create index hf_msgid on header_fields(value) "
3468 "where field=13", this );
3469 d->t->enqueue( d->q );
3474 if ( d->substate == 1 ) {
3475 if ( !d->q->done() )
3483 /*! Add a table to log connections. */
3485 bool Schema::stepTo68()
3487 if ( d->substate == 0 ) {
3488 describeStep( "Add a table to log connections." );
3490 "create table connections (id serial primary key,userid integer "
3491 "references users(id),client varchar not null,me