Don't check lockSanity at startup
[aox:aox.git] / db / schema.cpp
1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2
3 #include "schema.h"
4
5 #include "schemachecker.h"
6 #include "transaction.h"
7 #include "estringlist.h"
8 #include "allocator.h"
9 #include "postgres.h"
10 #include "address.h"
11 #include "ustring.h"
12 #include "granter.h"
13 #include "field.h"
14 #include "query.h"
15 #include "dict.h"
16 #include "log.h"
17 #include "md5.h"
18 #include "utf.h"
19 #include "mailbox.h"
20
21 #include <stdio.h>
22
23
24 class SchemaData
25     : public Garbage
26 {
27 public:
28     SchemaData()
29         : l( new Log ),
30           state( 0 ), substate( 0 ), revision( 0 ),
31           lock( 0 ), seq( 0 ), update( 0 ), q( 0 ),
32           t( 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 )
36     {
37         schema = Configuration::text( Configuration::DbSchema );
38         dbuser = Configuration::text( Configuration::DbUser ).quoted();
39     }
40
41     Log *l;
42     int state;
43     int substate;
44     uint revision;
45     Query *lock, *seq, *update, *q;
46     Transaction *t;
47     Query * result;
48     Query * unparsed;
49     bool upgrade;
50     bool commit;
51     EString version;
52     EString dbuser;
53     EString schema;
54
55     // The following state variables are needed by stepTo72().
56
57     Query * quid;
58     Query * undel;
59     Row * row;
60     uint lastMailbox;
61     uint count;
62     uint uidnext;
63     int64 nextmodseq;
64
65     Granter * granter;
66 };
67
68
69 /*! \class Schema schema.h
70
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.
74 */
75
76
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.)
81
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.
85
86     The \a owner will be notified of progress via the Query returned by
87     result().
88 */
89
90 Schema::Schema( EventHandler * owner, bool upgrade, bool commit )
91     : d( new SchemaData )
92 {
93     d->result = new Query( owner );
94     d->upgrade = upgrade;
95     d->commit = commit;
96     d->t = new Transaction( this );
97 }
98
99
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.
103 */
104
105 Query * Schema::result() const
106 {
107     return d->result;
108 }
109
110
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.
114
115     If the schema is not compatible, a disaster is logged.
116
117     The function expects to be called from ::main(), and should be the
118     first database transaction.
119 */
120
121 void Schema::checkRevision( EventHandler * owner )
122 {
123     Schema * s = new Schema( owner );
124     s->execute();
125 }
126
127
128 /*! After execute() has completed, this function returns the version
129     ("8.1.3") of the running Postgres server.
130 */
131
132 EString Schema::serverVersion() const
133 {
134     return d->version;
135 }
136
137
138 /*! Checks or upgrades the schema as required. */
139
140 void Schema::execute()
141 {
142     if ( d->state == 0 ) {
143         if ( d->upgrade ) {
144             d->lock =
145                 new Query( "select version() as version, revision from "
146                            "mailstore for update", this );
147             d->t->enqueue( d->lock );
148             d->t->execute();
149         }
150         else {
151             d->lock =
152                 new Query( "select version() as version, revision from "
153                            "mailstore", this );
154             d->lock->execute();
155         }
156         d->state = 1;
157     }
158
159     if ( d->state == 1 ) {
160         if ( !d->lock->done() )
161             return;
162
163         Row *r = d->lock->nextRow();
164         if ( r ) {
165             d->version
166                 = r->getEString( "version" ).simplified().section( " ", 2 );
167             d->revision = r->getInt( "revision" );
168         }
169
170         if ( !r || d->lock->failed() ) {
171             fail( "Bad database: Couldn't query the mailstore table.",
172                   d->lock );
173             d->revision = Database::currentRevision();
174             d->t->commit();
175             d->state = 7;
176         }
177         else if ( d->revision == Database::currentRevision() ) {
178             if ( d->upgrade ) {
179                 d->l->log( "Schema is already at revision " +
180                            fn( Database::currentRevision() ) +
181                            ", no upgrade necessary.",
182                            Log::Significant );
183                 d->t->commit();
184             }
185             d->result->setState( Query::Completed );
186             d->state = 7;
187         }
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() ) + ".",
193                        Log::Significant );
194             d->state = 2;
195         }
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() ) + ".",
200                        Log::Significant );
201             d->state = 2;
202         }
203         else {
204             EString s( "The existing schema (revision " );
205             s.appendNumber( d->revision );
206             s.append( ") is " );
207             if ( d->revision < Database::currentRevision() )
208                 s.append( "older" );
209             else
210                 s.append( "newer" );
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'" );
219             else
220                 s.append( "upgrade" );
221             s.append( " or contact support." );
222             fail( s );
223             d->revision = Database::currentRevision();
224             d->t->commit();
225             d->state = 7;
226         }
227     }
228
229     if ( d->upgrade &&
230          d->revision >= 85 &&
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 ) );
237             d->revision--;
238         }
239         Query * q = new Query( "update mailstore set revision=$1", 0 );
240         q->bind( 1, Database::currentRevision() );
241         d->t->enqueue( q );
242         d->state = 5;
243     }
244
245     while ( d->revision < Database::currentRevision() ) {
246         if ( d->state == 2 ) {
247             if ( !singleStep() )
248                 return;
249             d->state = 3;
250         }
251
252         if ( d->state == 3 ) {
253             d->update =
254                 new Query( "update mailstore set revision=revision+1",
255                            this );
256             d->t->enqueue( d->update );
257             d->t->execute();
258             d->state = 4;
259         }
260
261         if ( d->state == 4 ) {
262             if ( !d->update->done() )
263                 return;
264
265             d->l->log( "Done.", Log::Debug );
266             d->substate = 0;
267             d->state = 2;
268             d->revision++;
269
270             if ( d->revision == Database::currentRevision() ) {
271                 d->state = 5;
272                 break;
273             }
274         }
275     }
276
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",
281                        Log::Significant );
282         }
283
284         d->l->log( "Checking database, adjusting privileges.",
285                    Log::Significant );
286         Granter * g = new Granter( d->dbuser.unquoted(), d->t );
287         g->notify();
288
289         // SchemaChecker * c = new SchemaChecker( d->t );
290         // c->notify();
291
292         d->state = 6;
293         if ( d->commit )
294             d->t->commit();
295         else
296             d->t->rollback();
297     }
298
299     if ( d->state == 6 ) {
300         if ( !d->t->done() )
301             return;
302
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();
307         }
308
309         if ( d->unparsed && !d->unparsed->done() )
310             return;
311
312         if ( d->unparsed && d->unparsed->hasResults() ) {
313             Row * r = d->unparsed->nextRow();
314             int64 u = r->getBigint( "unparsed" );
315             if ( u )
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. ",
319                            Log::Significant );
320         }
321
322         d->state = 7;
323     }
324
325     if ( d->state == 7 ) {
326         if ( !d->t->done() )
327             return;
328
329         if ( d->t->failed() && !d->result->failed() ) {
330             EString s;
331             if ( d->upgrade )
332                 s = "The schema could not be upgraded to revision " +
333                     fn( Database::currentRevision() ) + ".";
334             else
335                 s = "The schema could not be validated.";
336             fail( s, d->t->failedQuery() );
337         }
338         else if ( d->upgrade ) {
339             EString s( "Schema upgraded to revision " );
340             s.appendNumber( Database::currentRevision() );
341             if ( !d->commit )
342                 s.append( ", but not committed" );
343             s.append( "." );
344
345
346             d->l->log( s, Log::Significant );
347             d->result->setState( Query::Completed );
348         }
349
350         d->state = 8;
351     }
352
353     if ( d->state == 8 ) {
354         d->state = 42;
355         d->result->notify();
356     }
357 }
358
359
360 /*! This private helper logs a \a description of the step currently
361     being made.
362 */
363
364 void Schema::describeStep( const EString & description )
365 {
366     d->l->log( fn( d->revision ) + "-" + fn( d->revision + 1 ) + ": " +
367                description, Log::Significant );
368 }
369
370
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.
375 */
376
377 void Schema::fail( const EString &s, Query * q )
378 {
379     d->result->setError( s );
380     d->l->log( s, Log::Disaster );
381     if ( q ) {
382         d->l->log( "Query: " + q->description(), Log::Disaster );
383         d->l->log( "Error: " + q->error(), Log::Disaster );
384     }
385 }
386
387
388 #include "downgrades.inc"
389
390
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
393     its work.
394 */
395
396 bool Schema::singleStep()
397 {
398     bool c;
399
400     switch ( d->revision ) {
401     case 1:
402         c = stepTo2(); break;
403     case 2:
404         c = stepTo3(); break;
405     case 3:
406         c = stepTo4(); break;
407     case 4:
408         c = stepTo5(); break;
409     case 5:
410         c = stepTo6(); break;
411     case 6:
412         c = stepTo7(); break;
413     case 7:
414         c = stepTo8(); break;
415     case 8:
416         c = stepTo9(); break;
417     case 9:
418         c = stepTo10(); break;
419     case 10:
420         c = stepTo11(); break;
421     case 11:
422         c = stepTo12(); break;
423     case 12:
424         c = stepTo13(); break;
425     case 13:
426         c = stepTo14(); break;
427     case 14:
428         c = stepTo15(); break;
429     case 15:
430         c = stepTo16(); break;
431     case 16:
432         c = stepTo17(); break;
433     case 17:
434         c = stepTo18(); break;
435     case 18:
436         c = stepTo19(); break;
437     case 19:
438         c = stepTo20(); break;
439     case 20:
440         c = stepTo21(); break;
441     case 21:
442         c = stepTo22(); break;
443     case 22:
444         c = stepTo23(); break;
445     case 23:
446         c = stepTo24(); break;
447     case 24:
448         c = stepTo25(); break;
449     case 25:
450         c = stepTo26(); break;
451     case 26:
452         c = stepTo27(); break;
453     case 27:
454         c = stepTo28(); break;
455     case 28:
456         c = stepTo29(); break;
457     case 29:
458         c = stepTo30(); break;
459     case 30:
460         c = stepTo31(); break;
461     case 31:
462         c = stepTo32(); break;
463     case 32:
464         c = stepTo33(); break;
465     case 33:
466         c = stepTo34(); break;
467     case 34:
468         c = stepTo35(); break;
469     case 35:
470         c = stepTo36(); break;
471     case 36:
472         c = stepTo37(); break;
473     case 37:
474         c = stepTo38(); break;
475     case 38:
476         c = stepTo39(); break;
477     case 39:
478         c = stepTo40(); break;
479     case 40:
480         c = stepTo41(); break;
481     case 41:
482         c = stepTo42(); break;
483     case 42:
484         c = stepTo43(); break;
485     case 43:
486         c = stepTo44(); break;
487     case 44:
488         c = stepTo45(); break;
489     case 45:
490         c = stepTo46(); break;
491     case 46:
492         c = stepTo47(); break;
493     case 47:
494         c = stepTo48(); break;
495     case 48:
496         c = stepTo49(); break;
497     case 49:
498         c = stepTo50(); break;
499     case 50:
500         c = stepTo51(); break;
501     case 51:
502         c = stepTo52(); break;
503     case 52:
504         c = stepTo53(); break;
505     case 53:
506         c = stepTo54(); break;
507     case 54:
508         c = stepTo55(); break;
509     case 55:
510         c = stepTo56(); break;
511     case 56:
512         c = stepTo57(); break;
513     case 57:
514         c = stepTo58(); break;
515     case 58:
516         c = stepTo59(); break;
517     case 59:
518         c = stepTo60(); break;
519     case 60:
520         c = stepTo61(); break;
521     case 61:
522         c = stepTo62(); break;
523     case 62:
524         c = stepTo63(); break;
525     case 63:
526         c = stepTo64(); break;
527     case 64:
528         c = stepTo65(); break;
529     case 65:
530         c = stepTo66(); break;
531     case 66:
532         c = stepTo67(); break;
533     case 67:
534         c = stepTo68(); break;
535     case 68:
536         c = stepTo69(); break;
537     case 69:
538         c = stepTo70(); break;
539     case 70:
540         c = stepTo71(); break;
541     case 71:
542         c = stepTo72(); break;
543     case 72:
544         c = stepTo73(); break;
545     case 73:
546         c = stepTo74(); break;
547     case 74:
548         c = stepTo75(); break;
549     case 75:
550         c = stepTo76(); break;
551     case 76:
552         c = stepTo77(); break;
553     case 77:
554         c = stepTo78(); break;
555     case 78:
556         c = stepTo79(); break;
557     case 79:
558         c = stepTo80(); break;
559     case 80:
560         c = stepTo81(); break;
561     case 81:
562         c = stepTo82(); break;
563     case 82:
564         c = stepTo83(); break;
565     case 83:
566         c = stepTo84(); break;
567     case 84:
568         c = stepTo85(); break;
569     case 85:
570         c = stepTo86(); break;
571     case 86:
572         c = stepTo87(); break;
573     case 87:
574         c = stepTo88(); break;
575     case 88:
576         c = stepTo89(); break;
577     case 89:
578         c = stepTo90(); break;
579     case 90:
580         c = stepTo91(); break;
581     case 91:
582         c = stepTo92(); break;
583     case 92:
584         c = stepTo93(); break;
585     case 93:
586         c = stepTo94(); break;
587     case 94:
588         c = stepTo95(); break;
589     case 95:
590         c = stepTo96(); break;
591     case 96:
592         c = stepTo97(); break;
593     default:
594         d->l->log( "Internal error. Reached impossible revision " +
595                    fn( d->revision ) + ".", Log::Disaster );
596         c = true;
597         break;
598     }
599     if ( d->revision >= 89 &&
600          d->revision <= numDowngradeFunctions &&
601          downgradeFunctions[d->revision] )
602         d->t->enqueue( downgradeFunctions[d->revision] );
603
604     return c;
605 }
606
607
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.
610 */
611
612 bool Schema::stepTo2()
613 {
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",
623                        this );
624         d->t->enqueue( d->q );
625         d->q = new Query( "alter table users add unique(login)",
626                        this );
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",
635                        this );
636         d->t->enqueue( d->q );
637         d->t->execute();
638         d->substate = 1;
639     }
640
641     if ( d->substate == 1 ) {
642         if ( !d->q->done() )
643             return false;
644     }
645
646     return true;
647 }
648
649
650 /*! Merges the binary_parts table into bodyparts. */
651
652 bool Schema::stepTo3()
653 {
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 );
670         d->t->execute();
671         d->substate = 1;
672     }
673
674     if ( d->substate == 1 ) {
675         while ( d->q->hasResults() ) {
676             Row *r = d->q->nextRow();
677             EString text, data;
678
679             Query *u =
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" );
684                 u->bindNull( 1 );
685                 u->bind( 2, MD5::hash( data ).hex() );
686             }
687             else {
688                 text = r->getEString( "text" );
689                 u->bind( 1, text );
690                 u->bind( 2, MD5::hash( text ).hex() );
691             }
692             u->bind( 3, r->getInt( "id" ) );
693             d->t->enqueue( u );
694         }
695
696         if ( !d->q->done() )
697             return false;
698
699         if ( d->q->rows() != 0 ) {
700             d->q = new Query( "fetch 512 from parts", this );
701             d->t->enqueue( d->q );
702             d->t->execute();
703             return false;
704         }
705         else {
706             d->substate = 2;
707             d->t->enqueue( new Query( "close parts", this ) );
708         }
709     }
710
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 );
720         d->t->execute();
721         d->substate = 3;
722     }
723
724     if ( d->substate == 3 ) {
725         if ( !d->q->done() )
726             return false;
727
728         EStringList ids;
729         Dict< uint > hashes;
730
731         while ( d->q->hasResults() ) {
732             Row *r = d->q->nextRow();
733             uint id = r->getInt( "id" );
734             EString hash = r->getEString( "hash" );
735
736             uint *old = hashes.find( hash );
737             if ( old ) {
738                 ids.append( fn( id ) );
739                 Query *u =
740                     new Query( "update part_numbers set "
741                                "bodypart=$1 where bodypart=$2", this );
742                 u->bind( 1, *old );
743                 u->bind( 2, id );
744                 d->t->enqueue( u );
745             }
746             else {
747                 uint * tmp
748                     = (uint*)Allocator::alloc( sizeof(uint), 0 );
749                 *tmp = id;
750                 hashes.insert( hash, tmp );
751             }
752         }
753
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 );
758         }
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 );
763         d->t->execute();
764         d->substate = 4;
765     }
766
767     if ( d->substate == 4 ) {
768         if ( !d->q->done() )
769             return false;
770     }
771
772     return true;
773 }
774
775
776 /*! Move message flags from the messages table to the extra_flags table,
777     now renamed just "flags".
778 */
779
780 bool Schema::stepTo4()
781 {
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 );
841         d->t->execute();
842         d->substate = 1;
843     }
844
845     if ( d->substate == 1 ) {
846         if ( !d->q->done() )
847             return false;
848     }
849
850     return true;
851 }
852
853
854 /*! Add some indices on header_fields, address_fields, and flags. */
855
856 bool Schema::stepTo5()
857 {
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)",
867                           this );
868         d->t->enqueue( d->q );
869         d->t->execute();
870         d->substate = 1;
871     }
872
873     if ( d->substate == 1 ) {
874         if ( !d->q->done() )
875             return false;
876     }
877
878     return true;
879 }
880
881
882 /*! Move bodyparts.bytes/lines to the part_numbers table. */
883
884 bool Schema::stepTo6()
885 {
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",
889                           this );
890         d->t->enqueue( d->q );
891         d->q = new Query( "alter table part_numbers add lines integer",
892                           this );
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 );
903         d->t->execute();
904         d->substate = 1;
905     }
906
907     if ( d->substate == 1 ) {
908         if ( !d->q->done() )
909             return false;
910     }
911
912     return true;
913 }
914
915
916 /*! Add header_fields.position. */
917
918 bool Schema::stepTo7()
919 {
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 );
936         d->t->execute();
937         d->substate = 1;
938     }
939
940     if ( d->substate == 1 ) {
941         while ( d->q->hasResults() ) {
942             Row *r = d->q->nextRow();
943
944             Query *u =
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)",
950                            this );
951             u->bind( 1, r->getInt( "mailbox" ) );
952             u->bind( 2, r->getInt( "uid" ) );
953             u->bind( 3, r->getEString( "part" ) );
954             d->t->enqueue( u );
955
956             u = new Query( "alter sequence hf_pos restart with 1", this );
957             d->t->enqueue( u );
958         }
959
960         if ( !d->q->done() )
961             return false;
962
963         if ( d->q->rows() != 0 ) {
964             d->q = new Query( "fetch 512 from groups", this );
965             d->t->enqueue( d->q );
966             d->t->execute();
967             return false;
968         }
969         else {
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 );
974             d->t->execute();
975             d->substate = 2;
976         }
977     }
978
979     if ( d->substate == 2 ) {
980         if ( !d->q->done() )
981             return false;
982     }
983
984     return true;
985 }
986
987
988 /*! Make address_fields refer to header_fields. */
989
990 bool Schema::stepTo8()
991 {
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 );
1021         d->t->execute();
1022         d->substate = 1;
1023     }
1024
1025     if ( d->substate == 1 ) {
1026         if ( !d->q->done() )
1027             return false;
1028     }
1029
1030     return true;
1031 }
1032
1033
1034 /*! Remove the recent_messages table altogether. */
1035
1036 bool Schema::stepTo9()
1037 {
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 );
1056         d->t->execute();
1057         d->substate = 1;
1058     }
1059
1060     if ( d->substate == 1 ) {
1061         if ( !d->q->done() )
1062             return false;
1063     }
1064
1065     return true;
1066 }
1067
1068
1069 /*! Add "on delete cascade" to the mailboxes.owner reference. */
1070
1071 bool Schema::stepTo10()
1072 {
1073     if ( d->substate == 0 ) {
1074         describeStep( "Altering mailboxes_owner_fkey." );
1075
1076         EString constraint = "mailboxes_owner_fkey";
1077         if ( d->version.startsWith( "7" ) )
1078             constraint = "$1";
1079
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 );
1088         d->t->execute();
1089         d->substate = 1;
1090     }
1091
1092     if ( d->substate == 1 ) {
1093         if ( !d->q->done() )
1094             return false;
1095     }
1096
1097     return true;
1098 }
1099
1100
1101 /*! Delete the revisions sequence. */
1102
1103 bool Schema::stepTo11()
1104 {
1105     if ( d->substate == 0 ) {
1106         describeStep( "Deleting revisions." );
1107         d->q = new Query( "drop sequence revisions", this );
1108         d->t->enqueue( d->q );
1109         d->t->execute();
1110         d->substate = 1;
1111     }
1112
1113     if ( d->substate == 1 ) {
1114         if ( !d->q->done() )
1115             return false;
1116     }
1117
1118     return true;
1119 }
1120
1121
1122 /*! Reverse stepTo10(). We don't want to delete rows in mailboxes. */
1123
1124 bool Schema::stepTo12()
1125 {
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 );
1135         d->t->execute();
1136         d->substate = 1;
1137     }
1138
1139     if ( d->substate == 1 ) {
1140         if ( !d->q->done() )
1141             return false;
1142     }
1143
1144     return true;
1145 }
1146
1147
1148 /*! Create the annotation_names and annotations tables. */
1149
1150 bool Schema::stepTo13()
1151 {
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)",
1156                           this );
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,"
1163                           "displayname text,"
1164                           "unique(mailbox,uid,owner,name),"
1165                           "foreign key (mailbox,uid) references "
1166                           "messages(mailbox,uid) on delete cascade)",
1167                           this );
1168         d->t->enqueue( d->q );
1169         d->t->execute();
1170         d->substate = 1;
1171     }
1172
1173     if ( d->substate == 1 ) {
1174         if ( !d->q->done() )
1175             return false;
1176     }
1177
1178     return true;
1179 }
1180
1181
1182 /*! Add the tables required to support views. */
1183
1184 bool Schema::stepTo14()
1185 {
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 );
1207         d->t->execute();
1208         d->substate = 1;
1209     }
1210
1211     if ( d->substate == 1 ) {
1212         if ( !d->q->done() )
1213             return false;
1214     }
1215
1216     return true;
1217 }
1218
1219
1220 /*! Add "on delete cascade" to the subscriptions/annotations.owner
1221     references.
1222 */
1223
1224 bool Schema::stepTo15()
1225 {
1226     if ( d->substate == 0 ) {
1227         describeStep( "Altering subscriptions_owner_fkey." );
1228
1229         EString ca( "subscriptions_owner_fkey" );
1230         EString cb( "annotations_owner_fkey" );
1231         if ( d->version.startsWith( "7" ) ) {
1232             ca = "$1";
1233             cb = "$1";
1234         }
1235
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 );
1252         d->t->execute();
1253         d->substate = 1;
1254     }
1255
1256     if ( d->substate == 1 ) {
1257         if ( !d->q->done() )
1258             return false;
1259     }
1260
1261     return true;
1262 }
1263
1264
1265 /*! Add the aliases table. */
1266
1267 bool Schema::stepTo16()
1268 {
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))",
1273                           this );
1274         d->t->enqueue( d->q );
1275         d->t->execute();
1276         d->substate = 1;
1277     }
1278
1279     if ( d->substate == 1 ) {
1280         if ( !d->q->done() )
1281             return false;
1282     }
1283
1284     return true;
1285 }
1286
1287
1288 /*! Drop the aliases table from #16 (never released) and recreate it,
1289     with a reference to the address, and a link from users.
1290 */
1291
1292 bool Schema::stepTo17()
1293 {
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 );
1319         d->t->execute();
1320         d->substate = 1;
1321     }
1322
1323     if ( d->substate == 1 ) {
1324         if ( !d->q->done() )
1325             return false;
1326     }
1327
1328     return true;
1329 }
1330
1331
1332 /*! Add the scripts table. */
1333
1334 bool Schema::stepTo18()
1335 {
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 );
1343         d->t->execute();
1344         d->substate = 1;
1345     }
1346
1347     if ( d->substate == 1 ) {
1348         if ( !d->q->done() )
1349             return false;
1350     }
1351
1352     return true;
1353 }
1354
1355
1356 /*! Add the date_fields table. */
1357
1358 bool Schema::stepTo19()
1359 {
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 )",
1367                           this );
1368         d->t->enqueue( d->q );
1369         d->t->execute();
1370         d->substate = 1;
1371     }
1372
1373     if ( d->substate == 1 ) {
1374         if ( !d->q->done() )
1375             return false;
1376     }
1377
1378     return true;
1379 }
1380
1381
1382 /*! Populate the date_fields table from header_fields. */
1383
1384 bool Schema::stepTo20()
1385 {
1386     describeStep( "(Not) Populating the date_fields table." );
1387     return true;
1388 }
1389
1390
1391 /*! Remove unnecessary stuff from annotations. */
1392
1393 bool Schema::stepTo21()
1394 {
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 );
1403         d->t->execute();
1404         d->substate = 1;
1405     }
1406
1407     if ( d->substate == 1 ) {
1408         if ( !d->q->done() )
1409             return false;
1410     }
1411
1412     return true;
1413 }
1414
1415
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.
1419 */
1420
1421 bool Schema::stepTo22()
1422 {
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 );
1430         d->t->execute();
1431         d->substate = 1;
1432     }
1433
1434     if ( d->substate == 1 ) {
1435         if ( !d->q->done() )
1436             return false;
1437
1438         if ( d->q->failed() ) {
1439             d->l->log( "Internal error.", Log::Debug );
1440             d->substate = 0;
1441             return true;
1442         }
1443
1444         d->l->log( "Changing case for " + fn( d->q->rows() ) + " flags.",
1445                    Log::Debug );
1446
1447         Row * r;
1448         while ( (r=d->q->nextRow()) != 0 ) {
1449             d->l->log( "Unbreaking " + r->getEString( "name" ) + ".",
1450                        Log::Debug );
1451
1452             Query * q;
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" ) );
1456             d->t->enqueue( q );
1457
1458             q = new Query( "delete from flag_names where id=$1", 0 );
1459             q->bind( 1, r->getInt( "from" ) );
1460             d->t->enqueue( q );
1461         }
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 );
1468         d->t->execute();
1469         d->substate = 2;
1470     }
1471
1472     if ( d->substate == 2 ) {
1473         if ( !d->q->done() )
1474             return false;
1475     }
1476
1477     return true;
1478 }
1479
1480
1481 /*! Add the deleted_messages table. */
1482
1483 bool Schema::stepTo23()
1484 {
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 )",
1494                           this );
1495         d->t->enqueue( d->q );
1496         d->t->execute();
1497         d->substate = 1;
1498     }
1499
1500     if ( d->substate == 1 ) {
1501         if ( !d->q->done() )
1502             return false;
1503     }
1504
1505     return true;
1506 }
1507
1508
1509 /*! Create threads/thread_members if they don't exist already. */
1510
1511 bool Schema::stepTo24()
1512 {
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 );
1518         d->t->execute();
1519         d->substate = 1;
1520     }
1521
1522     if ( d->substate == 1 ) {
1523         if ( !d->q->done() )
1524             return false;
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)",
1529                               this );
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 );
1537             d->t->execute();
1538             d->substate = 2;
1539         }
1540         else {
1541             d->substate = 0;
1542         }
1543     }
1544
1545     if ( d->substate == 2 ) {
1546         if ( !d->q->done() )
1547             return false;
1548     }
1549
1550     return true;
1551 }
1552
1553
1554 /*! Adds the modsequences table. */
1555
1556 bool Schema::stepTo25()
1557 {
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 " +
1563                           d->dbuser, this );
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 " +
1571                           d->dbuser, this );
1572         d->t->enqueue( d->q );
1573         d->t->execute();
1574         d->substate = 1;
1575     }
1576
1577     if ( d->substate == 1 ) {
1578         if ( !d->q->done() )
1579             return false;
1580     }
1581
1582     return true;
1583
1584 }
1585
1586
1587 /*! Alters deleted_messages.deleted_at to be a timestamp with time zone. */
1588
1589 bool Schema::stepTo26()
1590 {
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 "
1602                           "not null", this );
1603         d->t->enqueue( d->q );
1604         d->q = new Query( "alter table deleted_messages drop deleted_at",
1605                           this );
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 );
1610         d->t->execute();
1611         d->substate = 1;
1612     }
1613
1614     if ( d->substate == 1 ) {
1615         if ( !d->q->done() )
1616             return false;
1617     }
1618
1619     return true;
1620 }
1621
1622
1623 /*! Add "on delete cascade" to the mailboxes.owner reference. */
1624
1625 bool Schema::stepTo27()
1626 {
1627     if ( d->substate == 0 ) {
1628         describeStep( "Altering modsequences_mailbox_fkey." );
1629
1630         EString constraint = "modsequences_mailbox_fkey";
1631         if ( d->version.startsWith( "7" ) )
1632             constraint = "$1";
1633
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 );
1643         d->t->execute();
1644         d->substate = 1;
1645     }
1646
1647     if ( d->substate == 1 ) {
1648         if ( !d->q->done() )
1649             return false;
1650     }
1651
1652     return true;
1653 }
1654
1655
1656 /*! Create the deliveries table. */
1657
1658 bool Schema::stepTo28()
1659 {
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 );
1676         d->t->execute();
1677         d->substate = 1;
1678     }
1679
1680     if ( d->substate == 1 ) {
1681         if ( !d->q->done() )
1682             return false;
1683     }
1684
1685     return true;
1686 }
1687
1688
1689 /*! Replace views.suidnext with nextmodseq. */
1690
1691 bool Schema::stepTo29()
1692 {
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 );
1705         d->t->execute();
1706         d->substate = 1;
1707     }
1708
1709     if ( d->substate == 1 ) {
1710         if ( !d->q->done() )
1711             return false;
1712     }
1713
1714     return true;
1715 }
1716
1717
1718 /*! Create the access_keys table. */
1719
1720 bool Schema::stepTo30()
1721 {
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 );
1733         d->t->execute();
1734         d->substate = 1;
1735     }
1736
1737     if ( d->substate == 1 ) {
1738         if ( !d->q->done() )
1739             return false;
1740     }
1741
1742     return true;
1743 }
1744
1745
1746 /*! Add indexes on addresses and deleted_messages. */
1747
1748 bool Schema::stepTo31()
1749 {
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, "
1758                           "uid)", this );
1759         d->t->enqueue( d->q );
1760         d->q = new Query( "create index pn_b on part_numbers(bodypart)",
1761                           this );
1762         d->t->enqueue( d->q );
1763         d->t->execute();
1764         d->substate = 1;
1765     }
1766
1767     if ( d->substate == 1 ) {
1768         if ( !d->q->done() )
1769             return false;
1770     }
1771
1772     return true;
1773 }
1774
1775
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
1780     header_fields rows.
1781
1782     Well, actually it doesn't do the last step yet. The
1783     MessageHeaderFetcher is careful to disregard these rows, so the do
1784     no harm.
1785 */
1786
1787 bool Schema::stepTo32()
1788 {
1789     if ( d->substate == 0 ) {
1790         describeStep( "Numbering address_fields rows (slow)." );
1791
1792         d->q = new Query( "alter table address_fields add number integer", 0 );
1793         d->t->enqueue( d->q );
1794
1795         d->q = new Query( "set enable_mergejoin to false", 0 );
1796         d->t->enqueue( d->q );
1797
1798         d->q = new Query( "set enable_hashjoin to false", 0 );
1799         d->t->enqueue( d->q );
1800
1801         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 );
1809
1810         d->q = new Query( "set enable_mergejoin to true", 0 );
1811         d->t->enqueue( d->q );
1812
1813         d->q = new Query( "set enable_hashjoin to true", 0 );
1814         d->t->enqueue( d->q );
1815
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 );
1820
1821         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 );
1827
1828         EString constraint = "address_fields_mailbox_fkey";
1829         if ( d->version.startsWith( "7" ) )
1830             constraint = "$2";
1831
1832         d->q =
1833             new Query( "alter table address_fields drop constraint "
1834                        "\"" + constraint + "\"", 0 );
1835         d->t->enqueue( d->q );
1836
1837         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 );
1843
1844         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 );
1850
1851         d->q = new Query( "drop index hf_fp", this );
1852         d->t->enqueue( d->q );
1853
1854         d->t->execute();
1855         d->substate = 1;
1856     }
1857
1858     if ( d->substate == 1 ) {
1859         if ( !d->q->done() )
1860             return false;
1861     }
1862
1863     return true;
1864 }
1865
1866
1867 /*! Add some indexes to speed up message deletion. */
1868
1869 bool Schema::stepTo33()
1870 {
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)",
1874                           this );
1875         d->t->enqueue( d->q );
1876         d->q = new Query( "create index vm_mu on view_messages (source,suid)",
1877                           this );
1878         d->t->enqueue( d->q );
1879         d->q = new Query( "create index ms_mu on modsequences(mailbox,uid)",
1880                           this );
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 );
1887         d->t->execute();
1888         d->substate = 1;
1889     }
1890
1891     if ( d->substate == 1 ) {
1892         if ( !d->q->done() )
1893             return false;
1894     }
1895
1896     return true;
1897 }
1898
1899
1900 /*! Add tried_at to deliveries. */
1901
1902 bool Schema::stepTo34()
1903 {
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 );
1909         d->t->execute();
1910         d->substate = 1;
1911     }
1912
1913     if ( d->substate == 1 ) {
1914         if ( !d->q->done() )
1915             return false;
1916     }
1917
1918     return true;
1919 }
1920
1921
1922 /*! Add sender to deliveries too. */
1923
1924 bool Schema::stepTo35()
1925 {
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 "
1932                           "not null", this );
1933         d->t->enqueue( d->q );
1934         d->t->execute();
1935         d->substate = 1;
1936     }
1937
1938     if ( d->substate == 1 ) {
1939         if ( !d->q->done() )
1940             return false;
1941     }
1942
1943     return true;
1944 }
1945
1946
1947 /*! Grant "update" on deliveries to aox, because although stepTo28() did
1948     that, schema/grant-privileges did not.
1949 */
1950
1951 bool Schema::stepTo36()
1952 {
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 );
1957         d->t->execute();
1958         d->substate = 1;
1959     }
1960
1961     if ( d->substate == 1 ) {
1962         if ( !d->q->done() )
1963             return false;
1964     }
1965
1966     return true;
1967 }
1968
1969
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.
1973 */
1974
1975 bool Schema::stepTo37()
1976 {
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 );
1982         d->t->execute();
1983         d->substate = 1;
1984     }
1985
1986     if ( d->substate == 1 ) {
1987         if ( !d->q->done() )
1988             return false;
1989         if ( !d->q->hasResults() ) {
1990             d->q =
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 );
1996             d->t->execute();
1997             d->substate = 2;
1998         }
1999         else {
2000             d->substate = 0;
2001         }
2002     }
2003
2004     if ( d->substate == 2 ) {
2005         if ( !d->q->done() )
2006             return false;
2007     }
2008
2009     return true;
2010 }
2011
2012
2013 /*! Grant insert on unparsed_messages. */
2014
2015 bool Schema::stepTo38()
2016 {
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 );
2022         d->t->execute();
2023         d->substate = 1;
2024     }
2025
2026     if ( d->substate == 1 ) {
2027         if ( !d->q->done() )
2028             return false;
2029     }
2030
2031     return true;
2032 }
2033
2034
2035 /*! Add a unique constraint to scripts. */
2036
2037 bool Schema::stepTo39()
2038 {
2039     if ( d->substate == 0 ) {
2040         describeStep( "Adding unique constraint to scripts" );
2041         d->q =
2042             new Query( "alter table scripts add constraint "
2043                        "scripts_owner_key unique(owner,name)", this );
2044         d->t->enqueue( d->q );
2045         d->t->execute();
2046         d->substate = 1;
2047     }
2048
2049     if ( d->substate == 1 ) {
2050         if ( !d->q->done() )
2051             return false;
2052     }
2053
2054     return true;
2055 }
2056
2057
2058 /*! Relax the deleted_messages.deleted_by constraint. */
2059
2060 bool Schema::stepTo40()
2061 {
2062     if ( d->substate == 0 ) {
2063         describeStep( "Dropping NOT NULL on deleted_messages.deleted_by" );
2064         d->q =
2065             new Query( "alter table deleted_messages alter deleted_by "
2066                        "drop not null", this );
2067         d->t->enqueue( d->q );
2068         d->t->execute();
2069         d->substate = 1;
2070     }
2071
2072     if ( d->substate == 1 ) {
2073         if ( !d->q->done() )
2074             return false;
2075     }
2076
2077     return true;
2078 }
2079
2080
2081 /*! Populate unparsed_messages. */
2082
2083 bool Schema::stepTo41()
2084 {
2085     if ( d->substate == 0 ) {
2086         describeStep( "Populating unparsed_messages" );
2087         d->q =
2088             new Query(
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 "
2092                 "using (bodypart) "
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
2099             );
2100         d->t->enqueue( d->q );
2101         d->t->execute();
2102         d->substate = 1;
2103     }
2104
2105     if ( d->substate == 1 ) {
2106         if ( !d->q->done() )
2107             return false;
2108     }
2109
2110     return true;
2111 }
2112
2113
2114 /*! Insert modsequences for any messages that don't have them. */
2115
2116 bool Schema::stepTo42()
2117 {
2118     if ( d->substate == 0 ) {
2119         describeStep( "Populating modsequences for old messages" );
2120         d->q =
2121             new Query(
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
2126             );
2127         d->t->enqueue( d->q );
2128         d->t->execute();
2129         d->substate = 1;
2130     }
2131
2132     if ( d->substate == 1 ) {
2133         if ( !d->q->done() )
2134             return false;
2135     }
2136
2137     return true;
2138 }
2139
2140
2141 /*! Make nextmodseq be per-mailbox. */
2142
2143 bool Schema::stepTo43()
2144 {
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 );
2160         d->substate = 1;
2161         d->t->execute();
2162     }
2163
2164     if ( d->substate == 1 ) {
2165         if ( !d->q->done() )
2166             return false;
2167     }
2168
2169     return true;
2170 }
2171
2172
2173 /*! Add some primary keys (the easy ones). */
2174
2175 bool Schema::stepTo44()
2176 {
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 );
2199         d->substate = 1;
2200         d->t->execute();
2201     }
2202
2203     if ( d->substate == 1 ) {
2204         if ( !d->q->done() )
2205             return false;
2206     }
2207
2208     return true;
2209 }
2210
2211
2212 /*! Add an index on users.login. */
2213
2214 bool Schema::stepTo45()
2215 {
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 );
2220         d->substate = 1;
2221         d->t->execute();
2222     }
2223
2224     if ( d->substate == 1 ) {
2225         if ( !d->q->done() )
2226             return false;
2227     }
2228
2229     return true;
2230 }
2231
2232
2233 /*! Remove duplicates from deleted_messages, and add a primary key. */
2234
2235 bool Schema::stepTo46()
2236 {
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 );
2255         d->substate = 1;
2256         d->t->execute();
2257     }
2258
2259     if ( d->substate == 1 ) {
2260         if ( !d->q->done() )
2261             return false;
2262     }
2263
2264     return true;
2265 }
2266
2267
2268 /*! Unconstrain annotations.owner and add a surrogate key. */
2269
2270 bool Schema::stepTo47()
2271 {
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'))",
2284                           this );
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 );
2295         d->substate = 1;
2296         d->t->execute();
2297     }
2298
2299     if ( d->substate == 1 ) {
2300         if ( !d->q->done() )
2301             return false;
2302     }
2303
2304     return true;
2305 }
2306
2307
2308 /*! Grant select,update on annotations_id_seq. */
2309
2310 bool Schema::stepTo48()
2311 {
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 );
2317         d->substate = 1;
2318         d->t->execute();
2319     }
2320
2321     if ( d->substate == 1 ) {
2322         if ( !d->q->done() )
2323             return false;
2324     }
2325
2326     return true;
2327 }
2328
2329
2330 /*! Grant privileges on threads and thread_members. */
2331
2332 bool Schema::stepTo49()
2333 {
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 );
2345         d->substate = 1;
2346         d->t->execute();
2347     }
2348
2349     if ( d->substate == 1 ) {
2350         if ( !d->q->done() )
2351             return false;
2352     }
2353
2354     return true;
2355 }
2356
2357
2358 /*! Add deliveries.delivered_at. */
2359
2360 bool Schema::stepTo50()
2361 {
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 );
2367         d->substate = 1;
2368         d->t->execute();
2369     }
2370
2371     if ( d->substate == 1 ) {
2372         if ( !d->q->done() )
2373             return false;
2374     }
2375
2376     return true;
2377 }
2378
2379
2380 /*! Split delivery_recipients away from deliveries. */
2381
2382 bool Schema::stepTo51()
2383 {
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 );
2400         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 );
2406         d->substate = 1;
2407         d->t->execute();
2408     }
2409
2410     if ( d->substate == 1 ) {
2411         if ( !d->q->done() )
2412             return false;
2413     }
2414
2415     return true;
2416 }
2417
2418
2419 /*! Add delivery_recipients.action and last_attempt. */
2420
2421 bool Schema::stepTo52()
2422 {
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 );
2433         d->substate = 1;
2434         d->t->execute();
2435     }
2436
2437     if ( d->substate == 1 ) {
2438         if ( !d->q->done() )
2439             return false;
2440     }
2441
2442     return true;
2443 }
2444
2445
2446 /*! We need permissions on the delivery_recipients sequence too. */
2447
2448 bool Schema::stepTo53()
2449 {
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 );
2455         d->substate = 1;
2456         d->t->execute();
2457     }
2458
2459     if ( d->substate == 1 ) {
2460         if ( !d->q->done() )
2461             return false;
2462     }
2463
2464     return true;
2465 }
2466
2467
2468 /*! Make (mailbox,uid) unique in deliveries. */
2469
2470 bool Schema::stepTo54()
2471 {
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)",
2475                           this );
2476         d->t->enqueue( d->q );
2477         d->substate = 1;
2478         d->t->execute();
2479     }
2480
2481     if ( d->substate == 1 ) {
2482         if ( !d->q->done() )
2483             return false;
2484     }
2485
2486     return true;
2487 }
2488
2489
2490 /*! Convert mUTF-7 mailbox names to UTF-8. */
2491
2492 bool Schema::stepTo55()
2493 {
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 );
2499         d->update = 0;
2500         d->substate = 1;
2501         d->t->execute();
2502     }
2503
2504     if ( d->substate == 1 ) {
2505         while ( d->q->hasResults() ) {
2506             Row * r = d->q->nextRow();
2507
2508             MUtf7Codec mu;
2509             Utf8Codec u;
2510
2511             EString oldName( r->getEString( "name" ) );
2512             EString newName( u.fromUnicode( mu.toUnicode( oldName ) ) );
2513
2514             if ( mu.wellformed() && oldName != newName ) {
2515                 Query * q =
2516                     new Query( "update mailboxes set name=$1 "
2517                                "where id=$2", this );
2518                 d->update = q;
2519                 q->bind( 1, newName );
2520                 q->bind( 2, r->getInt( "id" ) );
2521                 d->t->enqueue( q );
2522                 d->t->execute();
2523             }
2524         }
2525
2526         if ( !d->q->done() )
2527             return false;
2528
2529         d->substate = 2;
2530     }
2531
2532     if ( d->substate == 2 ) {
2533         if ( d->update && !d->update->done() )
2534             return false;
2535     }
2536
2537     return true;
2538 }
2539
2540
2541 /*! Create the vacation_responses table. */
2542
2543 bool Schema::stepTo56()
2544 {
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 );
2560         d->t->execute();
2561         d->substate = 1;
2562     }
2563
2564     if ( d->substate == 1 ) {
2565         if ( !d->q->done() )
2566             return false;
2567     }
2568
2569     return true;
2570 }
2571
2572
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
2575     renamed.)
2576 */
2577
2578 bool Schema::stepTo57()
2579 {
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 );
2597         d->t->execute();
2598         d->substate = 1;
2599     }
2600
2601     if ( d->substate == 1 ) {
2602         if ( !d->q->done() )
2603             return false;
2604     }
2605
2606     return true;
2607 }
2608
2609
2610 /*! Add a missing "on delete cascade" clause to scripts. */
2611
2612 bool Schema::stepTo58()
2613 {
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",
2622                           this );
2623         d->t->enqueue( d->q );
2624         d->t->execute();
2625         d->substate = 1;
2626     }
2627
2628     if ( d->substate == 1 ) {
2629         if ( !d->q->done() )
2630             return false;
2631     }
2632
2633     return true;
2634 }
2635
2636
2637 /*! Delete duplicate addresses: By mistake the unique index used a
2638     case-sensitive domain. We keep the oldest version seen.
2639 */
2640
2641 bool Schema::stepTo59()
2642 {
2643     if ( d->substate == 0 ) {
2644         describeStep( "Deleting duplicate addresses." );
2645         d->q = new Query(
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 );
2653         d->t->execute();
2654         d->substate = 1;
2655     }
2656
2657     if ( d->substate == 1 ) {
2658         if ( !d->q->done() )
2659             return false;
2660
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" );
2673         EString dfa;
2674
2675         Query * q;
2676         Row * r = d->q->nextRow();
2677         while ( r ) {
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 );
2688             d->t->enqueue( q );
2689             q = new Query( aliases, 0 );
2690             q->bind( 1, original );
2691             q->bind( 1, duplicate );
2692             d->t->enqueue( q );
2693             q = new Query( deliveries, 0 );
2694             q->bind( 1, original );
2695             q->bind( 1, duplicate );
2696             d->t->enqueue( q );
2697             q = new Query( dr, 0 );
2698             q->bind( 1, original );
2699             q->bind( 1, duplicate );
2700             d->t->enqueue( q );
2701             q = new Query( arf, 0 );
2702             q->bind( 1, original );
2703             q->bind( 1, duplicate );
2704             d->t->enqueue( q );
2705             q = new Query( art, 0 );
2706             q->bind( 1, original );
2707             q->bind( 1, duplicate );
2708             d->t->enqueue( q );
2709             if ( dfa.isEmpty() )
2710                 dfa = "delete from addresses where id=";
2711             else
2712                 dfa.append( " or id=" );
2713             dfa.appendNumber( duplicate );
2714             r = d->q->nextRow();
2715         }
2716
2717         q = new Query( dfa, 0 );
2718         d->t->enqueue( q );
2719
2720         q = new Query( "alter table addresses drop constraint "
2721                        "addresses_name_key", 0 );
2722         d->t->enqueue( q );
2723         q = new Query( "create unique index addresses_nld_key "
2724                        "on addresses(name,localpart,lower(domain))",
2725                        this );
2726         d->t->enqueue( q );
2727         d->t->execute();
2728         d->substate = 2;
2729     }
2730
2731     if ( d->substate == 2 ) {
2732         if ( !d->q->done() )
2733             return false;
2734     }
2735
2736     return true;
2737 }
2738
2739
2740 /*! Split messages into two, and clean up the resulting mess. */
2741
2742 bool Schema::stepTo60()
2743 {
2744     if ( d->substate == 0 ) {
2745         describeStep( "Splitting the messages table (may be very slow)." );
2746
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.
2750
2751         describeStep( "1. Separating messages and mailbox_messages" );
2752
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 );
2759
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 );
2766
2767         d->q = new Query( "grant select,insert,update on mailbox_messages "
2768                           "to " + d->dbuser, this );
2769         d->t->enqueue( d->q );
2770
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 );
2778
2779         d->q = new Query( "alter table messages drop idate", this );
2780         d->t->enqueue( d->q );
2781
2782         // Fetch the names of all foreign key references to messages.
2783
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 );
2791
2792         d->substate = 1;
2793         d->t->execute();
2794     }
2795
2796     if ( d->substate == 1 ) {
2797         if ( !d->q->done() )
2798             return false;
2799
2800         describeStep( "2. Updating foreign key references" );
2801
2802         if ( d->q->failed() || d->q->rows() == 0 ) {
2803             fail( "Couldn't fetch references to messages", d->q );
2804             d->substate = 42;
2805         }
2806         else {
2807             Dict<EString> constraints;
2808
2809             while ( d->q->hasResults() ) {
2810                 Row * r = d->q->nextRow();
2811                 constraints.insert(
2812                     r->getEString( "relname" ),
2813                     new EString( r->getEString( "conname" ) )
2814                 );
2815             }
2816
2817             d->q = new Query( "alter table part_numbers drop constraint " +
2818                               constraints.find( "part_numbers" )->quoted(),
2819                               this );
2820             d->t->enqueue( d->q );
2821
2822             d->q = new Query( "alter table date_fields drop constraint " +
2823                               constraints.find( "date_fields" )->quoted(),
2824                               this );
2825             d->t->enqueue( d->q );
2826
2827             d->q = new Query( "alter table thread_members drop constraint " +
2828                               constraints.find( "thread_members" )->quoted(),
2829                               this );
2830             d->t->enqueue( d->q );
2831
2832             d->q = new Query( "alter table flags drop constraint " +
2833                               constraints.find( "flags" )->quoted(), this );
2834             d->t->enqueue( d->q );
2835
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 );
2840
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 );
2847
2848             d->q = new Query( "alter table annotations drop constraint " +
2849                               constraints.find( "annotations" )->quoted(),
2850                               this );
2851             d->t->enqueue( d->q );
2852
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",
2857                               this );
2858             d->t->enqueue( d->q );
2859
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 );
2866
2867             d->q = new Query( "alter table view_messages drop constraint " +
2868                               constraints.find( "view_messages" )->quoted(),
2869                               this );
2870             d->t->enqueue( d->q );
2871
2872             d->q = new Query( "alter table deleted_messages drop constraint " +
2873                               constraints.find( "deleted_messages" )->quoted(),
2874                               this );
2875             d->t->enqueue( d->q );
2876
2877             d->q = new Query( "alter table modsequences drop constraint " +
2878                               constraints.find( "modsequences" )->quoted(),
2879                               this );
2880             d->t->enqueue( d->q );
2881
2882             d->q = new Query( "alter table deliveries drop constraint " +
2883                               constraints.find( "deliveries" )->quoted(),
2884                               this );
2885             d->t->enqueue( d->q );
2886
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 );
2894
2895             d->substate = 2;
2896             d->t->execute();
2897         }
2898     }
2899
2900     if ( d->substate == 2 ) {
2901         if ( !d->q->done() )
2902             return false;
2903
2904         describeStep( "3. Updating part_numbers and "
2905                       "header/address/date_fields" );
2906
2907         if ( d->q->failed() || d->q->rows() == 0 ) {
2908             fail( "Couldn't fetch references to part_numbers", d->q );
2909             d->substate = 42;
2910         }
2911         else {
2912             Dict<EString> constraints;
2913
2914             while ( d->q->hasResults() ) {
2915                 Row * r = d->q->nextRow();
2916                 constraints.insert(
2917                     r->getEString( "relname" ),
2918                     new EString( r->getEString( "conname" ) )
2919                 );
2920             }
2921
2922             d->q = new Query( "alter table header_fields drop constraint " +
2923                               constraints.find( "header_fields" )->quoted(),
2924                               this );
2925             d->t->enqueue( d->q );
2926
2927             d->q = new Query( "alter table address_fields drop constraint " +
2928                               constraints.find( "address_fields" )->quoted(),
2929                               this );
2930             d->t->enqueue( d->q );
2931
2932             d->q = new Query( "alter table part_numbers add message "
2933                               "integer", this );
2934             d->t->enqueue( d->q );
2935
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 );
2941
2942             d->q = new Query( "alter table part_numbers alter message "
2943                               "set not null", this );
2944             d->t->enqueue( d->q );
2945
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 );
2951
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 );
2958
2959             d->substate = 3;
2960             d->t->execute();
2961         }
2962     }
2963
2964     if ( d->substate == 3 ) {
2965         if ( !d->q->done() )
2966             return false;
2967
2968         if ( d->q->failed() || d->q->rows() != 1 ) {
2969             fail( "Couldn't fetch primary key for part_numbers", d->q );
2970             d->substate = 42;
2971         }
2972         else {
2973             Row * r = d->q->nextRow();
2974
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)",
2980                               this );
2981             d->t->enqueue( d->q );
2982
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 );
2989
2990             d->substate = 4;
2991             d->t->execute();
2992         }
2993     }
2994
2995     if ( d->substate == 4 ) {
2996         if ( !d->q->done() )
2997             return false;
2998
2999         if ( d->q->failed() || d->q->rows() == 0 ) {
3000             fail( "Couldn't fetch unique constraint on header_fields", d->q );
3001             d->substate = 42;
3002         }
3003         else {
3004             Dict<EString> constraints;
3005
3006             while ( d->q->hasResults() ) {
3007                 Row * r = d->q->nextRow();
3008                 constraints.insert(
3009                     r->getEString( "relname" ),
3010                     new EString( r->getEString( "conname" ) )
3011                 );
3012             }
3013
3014             d->q = new Query( "alter table header_fields drop constraint " +
3015                               constraints.find( "header_fields" )->quoted(),
3016                               this );
3017             d->t->enqueue( d->q );
3018
3019             d->q = new Query( "alter table header_fields add message "
3020                               "integer", this );
3021             d->t->enqueue( d->q );
3022             d->q = new Query( "alter table address_fields add message "
3023                               "integer", this );
3024             d->t->enqueue( d->q );
3025             d->q = new Query( "alter table date_fields add message "
3026                               "integer", this );
3027             d->t->enqueue( d->q );
3028
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 "
3040                               "messages m where "
3041                               "date_fields.mailbox=m.mailbox and "
3042                               "date_fields.uid=m.uid", this );
3043             d->t->enqueue( d->q );
3044
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 );
3054
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 );
3061
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 );
3068
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 );
3075
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",
3091                               this );
3092             d->t->enqueue( d->q );
3093
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 );
3098
3099             d->q = new Query( "create index af_mp on address_fields "
3100                               "(message,part)", this );
3101             d->t->enqueue( d->q );
3102
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 );
3107
3108             d->substate = 5;
3109             d->t->execute();
3110         }
3111     }
3112
3113     if ( d->substate == 5 ) {
3114         if ( !d->q->done() )
3115             return false;
3116
3117         describeStep( "4. Updating deliveries" );
3118
3119         d->q = new Query( "alter table deliveries add message "
3120                           "integer", this );
3121         d->t->enqueue( d->q );
3122
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 );
3128
3129         d->q = new Query( "alter table deliveries alter message "
3130                           "set not null", this );
3131         d->t->enqueue( d->q );
3132
3133         d->q = new Query( "alter table deliveries drop mailbox", this );
3134         d->t->enqueue( d->q );
3135
3136         d->q = new Query( "alter table deliveries drop uid", this );
3137         d->t->enqueue( d->q );
3138
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)",
3146                           this );
3147         d->t->enqueue( d->q );
3148
3149         d->substate = 6;
3150         d->t->execute();
3151     }
3152
3153     if ( d->substate == 6 ) {
3154         if ( !d->q->done() )
3155             return false;
3156
3157         describeStep( "5. Updating deleted_messages" );
3158
3159         d->q = new Query( "alter table deleted_messages add message "
3160                           "integer", this );
3161         d->t->enqueue( d->q );
3162
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 );
3168
3169         d->q = new Query( "alter table deleted_messages alter message "
3170                           "set not null", this );
3171         d->t->enqueue( d->q );
3172
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 );
3181
3182         d->substate = 7;
3183         d->t->execute();
3184     }
3185
3186     if ( d->substate == 7 ) {
3187         if ( !d->q->done() )
3188             return false;
3189
3190         describeStep( "6. Dropping unnecessary tables and columns" );
3191
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 );
3196
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 );
3201
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 );
3208
3209         d->substate = 8;
3210         d->t->execute();
3211     }
3212
3213     if ( d->substate == 8 ) {
3214         if ( !d->q->done() )
3215             return false;
3216
3217         describeStep( "7. Miscellaneous changes" );
3218
3219         if ( d->q->failed() || d->q->rows() == 0 ) {
3220             fail( "Couldn't fetch unique constraint on users", d->q );
3221             d->substate = 42;
3222         }
3223         else {
3224             Row * r = d->q->nextRow();
3225
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 );
3234
3235             d->substate = 9;
3236             d->t->execute();
3237         }
3238     }
3239
3240     if ( d->substate == 9 ) {
3241         if ( !d->q->done() )
3242             return false;
3243
3244         d->substate = 42;
3245     }
3246
3247     if ( d->substate == 42 ) {
3248         if ( !d->q->done() )
3249             return false;
3250     }
3251
3252     return true;
3253 }
3254
3255
3256 /*! Grant select,update on messages_id_seq. */
3257
3258 bool Schema::stepTo61()
3259 {
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 );
3265         d->substate = 1;
3266         d->t->execute();
3267     }
3268
3269     if ( d->substate == 1 ) {
3270         if ( !d->q->done() )
3271             return false;
3272     }
3273
3274     return true;
3275 }
3276
3277
3278 /*! Create a trigger on deleted_messages to remove the message. */
3279
3280 bool Schema::stepTo62()
3281 {
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 );
3287         d->substate = 1;
3288         d->t->execute();
3289     }
3290
3291     if ( d->substate == 1 ) {
3292         if ( !d->q->done() )
3293             return false;
3294
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." );
3298             d->substate = 42;
3299         }
3300         else {
3301             d->q =
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 );
3308             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 );
3313             d->substate = 2;
3314             d->t->execute();
3315         }
3316     }
3317
3318     if ( d->substate == 2 ) {
3319         if ( !d->q->done() )
3320             return false;
3321     }
3322
3323     return true;
3324 }
3325
3326
3327 /*! Add deleted_messages.modseq. */
3328
3329 bool Schema::stepTo63()
3330 {
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 );
3343         d->substate = 1;
3344         d->t->execute();
3345     }
3346
3347     if ( d->substate == 1 ) {
3348         if ( !d->q->done() )
3349             return false;
3350     }
3351
3352     return true;
3353 }
3354
3355
3356 /*! Make deleted_messages.message cascade on delete. */
3357
3358 bool Schema::stepTo64()
3359 {
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 );
3370         d->substate = 1;
3371         d->t->execute();
3372     }
3373
3374     if ( d->substate == 1 ) {
3375         if ( !d->q->done() )
3376             return false;
3377     }
3378
3379     return true;
3380 }
3381
3382
3383 /*! Grant "update" on threads to aox, so that the threader can lock the
3384     table in exclusive mode.
3385 */
3386
3387 bool Schema::stepTo65()
3388 {
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 );
3393         d->substate = 1;
3394         d->t->execute();
3395     }
3396
3397     if ( d->substate == 1 ) {
3398         if ( !d->q->done() )
3399             return false;
3400     }
3401
3402     return true;
3403 }
3404
3405
3406 /*! Change the unique constraint on threads to include "mailbox". */
3407
3408 bool Schema::stepTo66()
3409 {
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 );
3418         d->substate = 1;
3419         d->t->execute();
3420     }
3421
3422     if ( d->substate == 1 ) {
3423         if ( !d->q->done() )
3424             return false;
3425
3426         if ( d->q->failed() || d->q->rows() == 0 ) {
3427             fail( "Couldn't fetch unique constraint on threads", d->q );
3428             d->substate = 42;
3429         }
3430         else {
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 );
3439             d->substate = 2;
3440             d->t->execute();
3441         }
3442     }
3443
3444     if ( d->substate == 2 ) {
3445         if ( !d->q->done() )
3446             return false;
3447     }
3448
3449     return true;
3450 }
3451
3452
3453 /*! Create a couple of new indexes to make "aox vacuum" faster, and help
3454     to look for specific message-ids.
3455 */
3456
3457 bool Schema::stepTo67()
3458 {
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 );
3470         d->substate = 1;
3471         d->t->execute();
3472     }
3473
3474     if ( d->substate == 1 ) {
3475         if ( !d->q->done() )
3476             return false;
3477     }
3478
3479     return true;
3480 }
3481
3482
3483 /*! Add a table to log connections. */
3484
3485 bool Schema::stepTo68()
3486 {
3487     if ( d->substate == 0 ) {
3488         describeStep( "Add a table to log connections." );
3489         d->q = new Query(
3490             "create table connections (id serial primary key,userid integer "
3491             "references users(id),client varchar not null,me