← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stub/launchpad/db-cleanups into lp:launchpad/db-devel

 

Stuart Bishop has proposed merging lp:~stub/launchpad/db-cleanups into lp:launchpad/db-devel with lp:~stub/launchpad/pending-db-changes as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~stub/launchpad/db-cleanups/+merge/84781

New database baseline, eliminating main uses of fti.py in the process.
-- 
The attached diff has been truncated due to its size.
https://code.launchpad.net/~stub/launchpad/db-cleanups/+merge/84781
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stub/launchpad/db-cleanups into lp:launchpad/db-devel.
=== modified file 'database/schema/Makefile'
--- database/schema/Makefile	2011-10-06 09:38:10 +0000
+++ database/schema/Makefile	2011-12-07 15:10:02 +0000
@@ -64,9 +64,9 @@
 # on production. It is generated using newbaseline.py in
 # bzr+ssh://devpad.canonical.com/code/stub/dbascripts
 #
-REV=2208
+REV=2209
 BASELINE=launchpad-${REV}-00-0.sql
-MD5SUM=12a258f8651ae3bba0c96ec8e62be1dd  launchpad-2208-00-0.sql
+MD5SUM=cc7a493c924196409a22392a16443d52  launchpad-2209-00-0.sql
 
 default: all
 
@@ -76,8 +76,6 @@
 	@ ${CREATEDB} ${EMPTY_DBNAME} ${TEMPLATE_WITH_TEST_SAMPLEDATA}
 	@ echo "* Loading sample data"
 	@ psql -v ON_ERROR_STOP=1 -d ${TEMPLATE_WITH_TEST_SAMPLEDATA} -f $(SAMPLEDATA) > /dev/null
-	@ echo "* Rebuilding full text indexes"
-	@ ${PYTHON} fti.py --force -q -d ${TEMPLATE_WITH_TEST_SAMPLEDATA}
 	@ echo "* Resetting sequences"
 	@ ${PYTHON} reset_sequences.py -d ${TEMPLATE_WITH_TEST_SAMPLEDATA}
 	@ echo "* Disabling autovacuum"
@@ -93,8 +91,6 @@
 	@ ${CREATEDB} ${EMPTY_DBNAME} ${TEMPLATE_WITH_DEV_SAMPLEDATA}
 	@ echo "* Loading sample data"
 	@ psql -v ON_ERROR_STOP=1 -d ${TEMPLATE_WITH_DEV_SAMPLEDATA} -f $(SAMPLEDATA_DEV) > /dev/null
-	@ echo "* Rebuilding full text indexes"
-	@ ${PYTHON} fti.py --force -q -d ${TEMPLATE_WITH_DEV_SAMPLEDATA}
 	@ echo "* Resetting sequences"
 	@ ${PYTHON} reset_sequences.py -d ${TEMPLATE_WITH_DEV_SAMPLEDATA}
 	@ echo "* Disabling autovacuum"
@@ -116,26 +112,10 @@
 	@ echo
 	@ echo "* Creating database \"$(EMPTY_DBNAME)\"."
 	@ ${CREATEDB} template0 ${EMPTY_DBNAME}
-	@ if ! `createlang -l ${EMPTY_DBNAME} | grep -qs plpythonu`; then \
-		echo "* Installing PL/PythonU"; \
-		createlang -d ${EMPTY_DBNAME} plpythonu; \
-	fi
-	@ if ! `createlang -l ${EMPTY_DBNAME} | grep -qs plpgsql`; then \
-		echo "* Installing PL/PgSQL"; \
-	    	createlang -d ${EMPTY_DBNAME} plpgsql; \
-	fi
-	@ echo "* Creating todrop schema"
-	@ psql -d ${EMPTY_DBNAME} -q -c "CREATE SCHEMA todrop;"
-	@ echo "* Creating functions"
-	@ psql -d ${EMPTY_DBNAME} -f trusted.sql   | grep : | cat
-	@ echo "* Installing tsearch2 into ts2 schema"
-	@ ${PYTHON} fti.py -q --setup-only -d ${EMPTY_DBNAME}
 	@ echo "* Loading base database schema"
 	@ psql -d ${EMPTY_DBNAME} -f ${BASELINE} | grep : | cat
 	@ echo "* Patching the database schema"
 	@ ${PYTHON} upgrade.py -d ${EMPTY_DBNAME}
-	@ echo "* Setting up full text indexes"
-	@ ${PYTHON} fti.py -q -d ${EMPTY_DBNAME}
 	@ echo "* Security setup"
 	@ ${PYTHON} security.py -q -d ${EMPTY_DBNAME}
 	@ echo "* Disabling autovacuum"

=== renamed file 'database/schema/patch-2208-00-0.sql' => 'database/schema/archive/patch-2208-00-0.sql'
=== renamed file 'database/schema/patch-2208-01-0.sql' => 'database/schema/archive/patch-2208-01-0.sql'
=== renamed file 'database/schema/patch-2208-01-1.sql' => 'database/schema/archive/patch-2208-01-1.sql'
=== renamed file 'database/schema/patch-2208-01-2.sql' => 'database/schema/archive/patch-2208-01-2.sql'
=== renamed file 'database/schema/patch-2208-01-3.sql' => 'database/schema/archive/patch-2208-01-3.sql'
=== renamed file 'database/schema/patch-2208-02-0.sql' => 'database/schema/archive/patch-2208-02-0.sql'
=== renamed file 'database/schema/patch-2208-03-0.sql' => 'database/schema/archive/patch-2208-03-0.sql'
=== renamed file 'database/schema/patch-2208-04-0.sql' => 'database/schema/archive/patch-2208-04-0.sql'
=== renamed file 'database/schema/patch-2208-05-0.sql' => 'database/schema/archive/patch-2208-05-0.sql'
=== renamed file 'database/schema/patch-2208-07-0.sql' => 'database/schema/archive/patch-2208-07-0.sql'
=== renamed file 'database/schema/patch-2208-08-0.sql' => 'database/schema/archive/patch-2208-08-0.sql'
=== renamed file 'database/schema/patch-2208-08-1.sql' => 'database/schema/archive/patch-2208-08-1.sql'
=== renamed file 'database/schema/patch-2208-08-2.sql' => 'database/schema/archive/patch-2208-08-2.sql'
=== renamed file 'database/schema/patch-2208-08-3.sql' => 'database/schema/archive/patch-2208-08-3.sql'
=== renamed file 'database/schema/patch-2208-09-0.sql' => 'database/schema/archive/patch-2208-09-0.sql'
=== renamed file 'database/schema/patch-2208-10-0.sql' => 'database/schema/archive/patch-2208-10-0.sql'
=== renamed file 'database/schema/patch-2208-12-0.sql' => 'database/schema/archive/patch-2208-12-0.sql'
=== renamed file 'database/schema/patch-2208-13-0.sql' => 'database/schema/archive/patch-2208-13-0.sql'
=== renamed file 'database/schema/patch-2208-14-0.sql' => 'database/schema/archive/patch-2208-14-0.sql'
=== renamed file 'database/schema/patch-2208-15-0.sql' => 'database/schema/archive/patch-2208-15-0.sql'
=== renamed file 'database/schema/patch-2208-16-0.sql' => 'database/schema/archive/patch-2208-16-0.sql'
=== renamed file 'database/schema/patch-2208-17-0.sql' => 'database/schema/archive/patch-2208-17-0.sql'
=== renamed file 'database/schema/patch-2208-18-0.sql' => 'database/schema/archive/patch-2208-18-0.sql'
=== renamed file 'database/schema/patch-2208-19-0.sql' => 'database/schema/archive/patch-2208-19-0.sql'
=== renamed file 'database/schema/patch-2208-20-0.sql' => 'database/schema/archive/patch-2208-20-0.sql'
=== renamed file 'database/schema/patch-2208-21-0.sql' => 'database/schema/archive/patch-2208-21-0.sql'
=== renamed file 'database/schema/patch-2208-22-0.sql' => 'database/schema/archive/patch-2208-22-0.sql'
=== renamed file 'database/schema/patch-2208-23-0.sql' => 'database/schema/archive/patch-2208-23-0.sql'
=== renamed file 'database/schema/patch-2208-24-0.sql' => 'database/schema/archive/patch-2208-24-0.sql'
=== renamed file 'database/schema/patch-2208-25-0.sql' => 'database/schema/archive/patch-2208-25-0.sql'
=== renamed file 'database/schema/patch-2208-26-0.sql' => 'database/schema/archive/patch-2208-26-0.sql'
=== renamed file 'database/schema/patch-2208-27-0.sql' => 'database/schema/archive/patch-2208-27-0.sql'
=== renamed file 'database/schema/patch-2208-28-0.sql' => 'database/schema/archive/patch-2208-28-0.sql'
=== renamed file 'database/schema/patch-2208-28-1.sql' => 'database/schema/archive/patch-2208-28-1.sql'
=== renamed file 'database/schema/patch-2208-29-0.sql' => 'database/schema/archive/patch-2208-29-0.sql'
=== renamed file 'database/schema/patch-2208-30-0.sql' => 'database/schema/archive/patch-2208-30-0.sql'
=== renamed file 'database/schema/patch-2208-31-0.sql' => 'database/schema/archive/patch-2208-31-0.sql'
=== renamed file 'database/schema/patch-2208-32-0.sql' => 'database/schema/archive/patch-2208-32-0.sql'
=== renamed file 'database/schema/patch-2208-33-0.sql' => 'database/schema/archive/patch-2208-33-0.sql'
=== renamed file 'database/schema/patch-2208-34-0.sql' => 'database/schema/archive/patch-2208-34-0.sql'
=== renamed file 'database/schema/patch-2208-35-0.sql' => 'database/schema/archive/patch-2208-35-0.sql'
=== renamed file 'database/schema/patch-2208-36-0.sql' => 'database/schema/archive/patch-2208-36-0.sql'
=== renamed file 'database/schema/patch-2208-37-0.sql' => 'database/schema/archive/patch-2208-37-0.sql'
=== renamed file 'database/schema/patch-2208-38-0.sql' => 'database/schema/archive/patch-2208-38-0.sql'
=== renamed file 'database/schema/patch-2208-38-1.sql' => 'database/schema/archive/patch-2208-38-1.sql'
=== renamed file 'database/schema/patch-2208-39-0.sql' => 'database/schema/archive/patch-2208-39-0.sql'
=== renamed file 'database/schema/patch-2208-40-0.sql' => 'database/schema/archive/patch-2208-40-0.sql'
=== renamed file 'database/schema/patch-2208-41-0.sql' => 'database/schema/archive/patch-2208-41-0.sql'
=== renamed file 'database/schema/patch-2208-42-0.sql' => 'database/schema/archive/patch-2208-42-0.sql'
=== renamed file 'database/schema/patch-2208-43-0.sql' => 'database/schema/archive/patch-2208-43-0.sql'
=== renamed file 'database/schema/patch-2208-44-0.sql' => 'database/schema/archive/patch-2208-44-0.sql'
=== renamed file 'database/schema/patch-2208-45-0.sql' => 'database/schema/archive/patch-2208-45-0.sql'
=== renamed file 'database/schema/patch-2208-46-0.sql' => 'database/schema/archive/patch-2208-46-0.sql'
=== renamed file 'database/schema/patch-2208-47-0.sql' => 'database/schema/archive/patch-2208-47-0.sql'
=== renamed file 'database/schema/patch-2208-48-0.sql' => 'database/schema/archive/patch-2208-48-0.sql'
=== renamed file 'database/schema/patch-2208-49-0.sql' => 'database/schema/archive/patch-2208-49-0.sql'
=== renamed file 'database/schema/patch-2208-50-0.sql' => 'database/schema/archive/patch-2208-50-0.sql'
=== renamed file 'database/schema/patch-2208-51-0.sql' => 'database/schema/archive/patch-2208-51-0.sql'
=== renamed file 'database/schema/patch-2208-52-0.sql' => 'database/schema/archive/patch-2208-52-0.sql'
=== renamed file 'database/schema/patch-2208-53-0.sql' => 'database/schema/archive/patch-2208-53-0.sql'
=== renamed file 'database/schema/patch-2208-54-0.sql' => 'database/schema/archive/patch-2208-54-0.sql'
=== renamed file 'database/schema/patch-2208-55-0.sql' => 'database/schema/archive/patch-2208-55-0.sql'
=== renamed file 'database/schema/patch-2208-56-0.sql' => 'database/schema/archive/patch-2208-56-0.sql'
=== renamed file 'database/schema/patch-2208-57-0.sql' => 'database/schema/archive/patch-2208-57-0.sql'
=== renamed file 'database/schema/patch-2208-57-1.sql' => 'database/schema/archive/patch-2208-57-1.sql'
=== renamed file 'database/schema/patch-2208-58-0.sql' => 'database/schema/archive/patch-2208-58-0.sql'
=== renamed file 'database/schema/patch-2208-59-0.sql' => 'database/schema/archive/patch-2208-59-0.sql'
=== renamed file 'database/schema/patch-2208-59-1.sql' => 'database/schema/archive/patch-2208-59-1.sql'
=== renamed file 'database/schema/patch-2208-59-2.sql' => 'database/schema/archive/patch-2208-59-2.sql'
=== renamed file 'database/schema/patch-2208-60-0.sql' => 'database/schema/archive/patch-2208-60-0.sql'
=== renamed file 'database/schema/patch-2208-60-1.sql' => 'database/schema/archive/patch-2208-60-1.sql'
=== renamed file 'database/schema/patch-2208-61-0.sql' => 'database/schema/archive/patch-2208-61-0.sql'
=== renamed file 'database/schema/patch-2208-62-0.sql' => 'database/schema/archive/patch-2208-62-0.sql'
=== renamed file 'database/schema/patch-2208-63-0.sql' => 'database/schema/archive/patch-2208-63-0.sql'
=== renamed file 'database/schema/patch-2208-63-1.sql' => 'database/schema/archive/patch-2208-63-1.sql'
=== renamed file 'database/schema/patch-2208-63-2.sql' => 'database/schema/archive/patch-2208-63-2.sql'
=== renamed file 'database/schema/patch-2208-63-3.sql' => 'database/schema/archive/patch-2208-63-3.sql'
=== renamed file 'database/schema/patch-2208-63-4.sql' => 'database/schema/archive/patch-2208-63-4.sql'
=== renamed file 'database/schema/patch-2208-64-0.sql' => 'database/schema/archive/patch-2208-64-0.sql'
=== renamed file 'database/schema/patch-2208-65-0.sql' => 'database/schema/archive/patch-2208-65-0.sql'
=== renamed file 'database/schema/patch-2208-65-1.sql' => 'database/schema/archive/patch-2208-65-1.sql'
=== renamed file 'database/schema/patch-2208-65-2.sql' => 'database/schema/archive/patch-2208-65-2.sql'
=== renamed file 'database/schema/patch-2208-67-0.sql' => 'database/schema/archive/patch-2208-67-0.sql'
=== renamed file 'database/schema/patch-2208-68-0.sql' => 'database/schema/archive/patch-2208-68-0.sql'
=== renamed file 'database/schema/patch-2208-69-0.sql' => 'database/schema/archive/patch-2208-69-0.sql'
=== renamed file 'database/schema/patch-2208-70-0.sql' => 'database/schema/archive/patch-2208-70-0.sql'
=== renamed file 'database/schema/patch-2208-71-0.sql' => 'database/schema/archive/patch-2208-71-0.sql'
=== renamed file 'database/schema/patch-2208-72-0.sql' => 'database/schema/archive/patch-2208-72-0.sql'
=== renamed file 'database/schema/patch-2208-73-0.sql' => 'database/schema/archive/patch-2208-73-0.sql'
=== renamed file 'database/schema/patch-2208-73-1.sql' => 'database/schema/archive/patch-2208-73-1.sql'
=== renamed file 'database/schema/patch-2208-73-2.sql' => 'database/schema/archive/patch-2208-73-2.sql'
=== renamed file 'database/schema/patch-2208-74-0.sql' => 'database/schema/archive/patch-2208-74-0.sql'
=== renamed file 'database/schema/patch-2208-75-0.sql' => 'database/schema/archive/patch-2208-75-0.sql'
=== renamed file 'database/schema/patch-2208-75-1.sql' => 'database/schema/archive/patch-2208-75-1.sql'
=== renamed file 'database/schema/patch-2208-76-0.sql' => 'database/schema/archive/patch-2208-76-0.sql'
=== renamed file 'database/schema/patch-2208-76-1.sql' => 'database/schema/archive/patch-2208-76-1.sql'
=== renamed file 'database/schema/patch-2208-76-2.sql' => 'database/schema/archive/patch-2208-76-2.sql'
=== renamed file 'database/schema/patch-2208-76-3.sql' => 'database/schema/archive/patch-2208-76-3.sql'
=== renamed file 'database/schema/patch-2208-76-4.sql' => 'database/schema/archive/patch-2208-76-4.sql'
=== renamed file 'database/schema/patch-2208-77-0.sql' => 'database/schema/archive/patch-2208-77-0.sql'
=== renamed file 'database/schema/patch-2208-78-0.sql' => 'database/schema/archive/patch-2208-78-0.sql'
=== renamed file 'database/schema/patch-2208-78-1.sql' => 'database/schema/archive/patch-2208-78-1.sql'
=== renamed file 'database/schema/patch-2208-78-2.sql' => 'database/schema/archive/patch-2208-78-2.sql'
=== renamed file 'database/schema/patch-2208-79-0.sql' => 'database/schema/archive/patch-2208-79-0.sql'
=== renamed file 'database/schema/patch-2208-79-1.sql' => 'database/schema/archive/patch-2208-79-1.sql'
=== renamed file 'database/schema/patch-2208-80-1.sql' => 'database/schema/archive/patch-2208-80-1.sql'
=== renamed file 'database/schema/patch-2208-81-1.sql' => 'database/schema/archive/patch-2208-81-1.sql'
=== renamed file 'database/schema/patch-2208-82-1.sql' => 'database/schema/archive/patch-2208-82-1.sql'
=== renamed file 'database/schema/patch-2208-83-1.sql' => 'database/schema/archive/patch-2208-83-1.sql'
=== renamed file 'database/schema/patch-2208-83-2.sql' => 'database/schema/archive/patch-2208-83-2.sql'
=== renamed file 'database/schema/patch-2208-84-1.sql' => 'database/schema/archive/patch-2208-84-1.sql'
=== renamed file 'database/schema/patch-2208-85-1.sql' => 'database/schema/archive/patch-2208-85-1.sql'
=== renamed file 'database/schema/patch-2208-87-1.sql' => 'database/schema/archive/patch-2208-87-1.sql'
=== renamed file 'database/schema/patch-2208-87-2.sql' => 'database/schema/archive/patch-2208-87-2.sql'
=== renamed file 'database/schema/patch-2208-88-1.sql' => 'database/schema/archive/patch-2208-88-1.sql'
=== renamed file 'database/schema/patch-2208-89-1.sql' => 'database/schema/archive/patch-2208-89-1.sql'
=== renamed file 'database/schema/patch-2208-90-1.sql' => 'database/schema/archive/patch-2208-90-1.sql'
=== renamed file 'database/schema/patch-2208-91-1.sql' => 'database/schema/archive/patch-2208-91-1.sql'
=== renamed file 'database/schema/patch-2208-92-1.sql' => 'database/schema/archive/patch-2208-92-1.sql'
=== renamed file 'database/schema/patch-2208-92-2.sql' => 'database/schema/archive/patch-2208-92-2.sql'
=== renamed file 'database/schema/patch-2208-93-0.sql' => 'database/schema/archive/patch-2208-93-0.sql'
=== renamed file 'database/schema/patch-2208-93-1.sql' => 'database/schema/archive/patch-2208-93-1.sql'
=== renamed file 'database/schema/patch-2208-93-2.sql' => 'database/schema/archive/patch-2208-93-2.sql'
=== renamed file 'database/schema/launchpad-2208-00-0.sql' => 'database/schema/launchpad-2209-00-0.sql'
--- database/schema/launchpad-2208-00-0.sql	2011-01-31 11:10:39 +0000
+++ database/schema/launchpad-2209-00-0.sql	2011-12-07 15:10:02 +0000
@@ -1,16 +1,65 @@
--- Generated Tue Aug 17 10:52:11 2010 UTC
+-- Generated Tue Dec  6 20:57:32 2011 UTC
 
 SET client_min_messages TO ERROR;
-
-
+SET statement_timeout = 0;
 SET client_encoding = 'UTF8';
 SET standard_conforming_strings = off;
 SET check_function_bodies = false;
 SET client_min_messages = warning;
 SET escape_string_warning = off;
 
+CREATE SCHEMA todrop;
+
+
+CREATE SCHEMA ts2;
+
+
+CREATE PROCEDURAL LANGUAGE plpgsql;
+
+
+CREATE PROCEDURAL LANGUAGE plpythonu;
+
+
 SET search_path = public, pg_catalog;
 
+CREATE TYPE debversion;
+
+
+CREATE FUNCTION debversionin(cstring) RETURNS debversion
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$textin$$;
+
+
+CREATE FUNCTION debversionout(debversion) RETURNS cstring
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$textout$$;
+
+
+CREATE FUNCTION debversionrecv(internal) RETURNS debversion
+    LANGUAGE internal STABLE STRICT
+    AS $$textrecv$$;
+
+
+CREATE FUNCTION debversionsend(debversion) RETURNS bytea
+    LANGUAGE internal STABLE STRICT
+    AS $$textsend$$;
+
+
+CREATE TYPE debversion (
+    INTERNALLENGTH = variable,
+    INPUT = debversionin,
+    OUTPUT = debversionout,
+    RECEIVE = debversionrecv,
+    SEND = debversionsend,
+    CATEGORY = 'S',
+    ALIGNMENT = int4,
+    STORAGE = extended
+);
+
+
+COMMENT ON TYPE debversion IS 'Debian package version number';
+
+
 CREATE TYPE pgstattuple_type AS (
 	table_len bigint,
 	tuple_count bigint,
@@ -23,38 +72,4489 @@
 	free_percent double precision
 );
 
+
+SET search_path = ts2, pg_catalog;
+
+CREATE DOMAIN gtsq AS text;
+
+
+CREATE DOMAIN gtsvector AS pg_catalog.gtsvector;
+
+
+CREATE TYPE statinfo AS (
+	word text,
+	ndoc integer,
+	nentry integer
+);
+
+
+CREATE TYPE tokenout AS (
+	tokid integer,
+	token text
+);
+
+
+CREATE TYPE tokentype AS (
+	tokid integer,
+	alias text,
+	descr text
+);
+
+
+CREATE TYPE tsdebug AS (
+	ts_name text,
+	tok_type text,
+	description text,
+	token text,
+	dict_name text[],
+	tsvector pg_catalog.tsvector
+);
+
+
+CREATE DOMAIN tsquery AS pg_catalog.tsquery;
+
+
+CREATE DOMAIN tsvector AS pg_catalog.tsvector;
+
+
+SET search_path = public, pg_catalog;
+
+CREATE FUNCTION activity() RETURNS SETOF pg_stat_activity
+    LANGUAGE sql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+    SELECT
+        datid, datname, procpid, usesysid, usename,
+        CASE
+            WHEN current_query LIKE '<IDLE>%'
+                OR current_query LIKE 'autovacuum:%'
+                THEN current_query
+            ELSE
+                '<HIDDEN>'
+        END AS current_query,
+        waiting, xact_start, query_start,
+        backend_start, client_addr, client_port
+    FROM pg_catalog.pg_stat_activity;
+$$;
+
+
+COMMENT ON FUNCTION activity() IS 'SECURITY DEFINER wrapper around pg_stat_activity allowing unprivileged users to access most of its information.';
+
+
+CREATE FUNCTION add_test_openid_identifier(account_ integer) RETURNS boolean
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    -- The generated OpenIdIdentifier is not a valid OpenId Identity URL
+    -- and does not match tokens generated by the Canonical SSO. They
+    -- are only useful to the test suite, and access to this stored
+    -- procedure on production does not allow you to compromise
+    -- accounts.
+    INSERT INTO OpenIdIdentifier (identifier, account)
+    VALUES ('test' || CAST(account_ AS text), account_);
+    RETURN TRUE;
+EXCEPTION
+    WHEN unique_violation THEN
+        RETURN FALSE;
+END;
+$$;
+
+
+COMMENT ON FUNCTION add_test_openid_identifier(account_ integer) IS 'Add an OpenIdIdentifier to an account that can be used to login in the test environment. These identifiers are not usable on production or staging.';
+
+
+CREATE FUNCTION assert_patch_applied(major integer, minor integer, patch integer) RETURNS boolean
+    LANGUAGE plpythonu STABLE
+    AS $$
+    rv = plpy.execute("""
+        SELECT * FROM LaunchpadDatabaseRevision
+        WHERE major=%d AND minor=%d AND patch=%d
+        """ % (major, minor, patch))
+    if len(rv) == 0:
+        raise Exception(
+            'patch-%d-%02d-%d not applied.' % (major, minor, patch))
+    else:
+        return True
+$$;
+
+
+COMMENT ON FUNCTION assert_patch_applied(major integer, minor integer, patch integer) IS 'Raise an exception if the given database patch has not been applied.';
+
+
+CREATE FUNCTION bug_maintain_bug_summary() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    -- There is no INSERT logic, as a bug will not have any summary
+    -- information until BugTask rows have been attached.
+    IF TG_OP = 'UPDATE' THEN
+        IF OLD.duplicateof IS DISTINCT FROM NEW.duplicateof
+            OR OLD.private IS DISTINCT FROM NEW.private
+            OR (OLD.latest_patch_uploaded IS NULL)
+                <> (NEW.latest_patch_uploaded IS NULL) THEN
+            PERFORM unsummarise_bug(OLD);
+            PERFORM summarise_bug(NEW);
+        END IF;
+
+    ELSIF TG_OP = 'DELETE' THEN
+        PERFORM unsummarise_bug(OLD);
+    END IF;
+
+    PERFORM bug_summary_flush_temp_journal();
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION bug_maintain_bug_summary() IS 'AFTER trigger on bug maintaining the bugs summaries in bugsummary.';
+
+
+CREATE FUNCTION valid_bug_name(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $_$
+    import re
+    name = args[0]
+    pat = r"^[a-z][a-z0-9+\.\-]+$"
+    if re.match(pat, name):
+        return 1
+    return 0
+$_$;
+
+
+COMMENT ON FUNCTION valid_bug_name(text) IS 'validate a bug name
+
+    As per valid_name, except numeric-only names are not allowed (including
+    names that look like floats).';
+
+
 SET default_tablespace = '';
 
 SET default_with_oids = false;
 
-CREATE TABLE revision (
-    id integer NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
-    log_body text NOT NULL,
-    revision_author integer NOT NULL,
-    gpgkey integer,
-    revision_id text NOT NULL,
-    revision_date timestamp without time zone,
-    karma_allocated boolean DEFAULT false
-);
-ALTER TABLE ONLY revision ALTER COLUMN revision_author SET STATISTICS 500;
-ALTER TABLE ONLY revision ALTER COLUMN revision_date SET STATISTICS 500;
+CREATE TABLE bug (
+    id integer NOT NULL,
+    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
+    name text,
+    title text NOT NULL,
+    description text NOT NULL,
+    owner integer NOT NULL,
+    duplicateof integer,
+    fti ts2.tsvector,
+    private boolean DEFAULT false NOT NULL,
+    security_related boolean DEFAULT false NOT NULL,
+    date_last_updated timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
+    date_made_private timestamp without time zone,
+    who_made_private integer,
+    date_last_message timestamp without time zone,
+    number_of_duplicates integer DEFAULT 0 NOT NULL,
+    message_count integer DEFAULT 0 NOT NULL,
+    users_affected_count integer DEFAULT 0,
+    users_unaffected_count integer DEFAULT 0,
+    heat integer DEFAULT 0 NOT NULL,
+    heat_last_updated timestamp without time zone,
+    latest_patch_uploaded timestamp without time zone,
+    access_policy integer,
+    CONSTRAINT notduplicateofself CHECK ((NOT (id = duplicateof))),
+    CONSTRAINT sane_description CHECK (((ltrim(description) <> ''::text) AND (char_length(description) <= 50000))),
+    CONSTRAINT valid_bug_name CHECK (valid_bug_name(name))
+);
+
+
+COMMENT ON TABLE bug IS 'A software bug that requires fixing. This particular bug may be linked to one or more products or source packages to identify the location(s) that this bug is found.';
+
+
+COMMENT ON COLUMN bug.name IS 'A lowercase name uniquely identifying the bug';
+
+
+COMMENT ON COLUMN bug.description IS 'A detailed description of the bug. Initially this will be set to the contents of the initial email or bug filing comment, but later it can be edited to give a more accurate description of the bug itself rather than the symptoms observed by the reporter.';
+
+
+COMMENT ON COLUMN bug.private IS 'Is this bug private? If so, only explicit subscribers will be able to see it';
+
+
+COMMENT ON COLUMN bug.security_related IS 'Is this bug a security issue?';
+
+
+COMMENT ON COLUMN bug.date_last_message IS 'When the last BugMessage was attached to this Bug. Maintained by a trigger on the BugMessage table.';
+
+
+COMMENT ON COLUMN bug.number_of_duplicates IS 'The number of bugs marked as duplicates of this bug, populated by a trigger after setting the duplicateof of bugs.';
+
+
+COMMENT ON COLUMN bug.message_count IS 'The number of messages (currently just comments) on this bugbug, maintained by the set_bug_message_count_t trigger.';
+
+
+COMMENT ON COLUMN bug.users_affected_count IS 'The number of users affected by this bug, maintained by the set_bug_users_affected_count_t trigger.';
+
+
+COMMENT ON COLUMN bug.heat IS 'The relevance of this bug. This value is computed periodically using bug_affects_person and other bug values.';
+
+
+COMMENT ON COLUMN bug.heat_last_updated IS 'The time this bug''s heat was last updated, or NULL if the heat has never yet been updated.';
+
+
+COMMENT ON COLUMN bug.latest_patch_uploaded IS 'The time when the most recent patch has been attached to this bug or NULL if no patches are attached';
+
+
+CREATE FUNCTION bug_row(bug_id integer) RETURNS bug
+    LANGUAGE sql STABLE
+    AS $_$
+    SELECT * FROM Bug WHERE id=$1;
+$_$;
+
+
+COMMENT ON FUNCTION bug_row(bug_id integer) IS 'Helper for manually testing functions requiring a bug row as input. eg. SELECT * FROM bugsummary_tags(bug_row(1))';
+
+
+CREATE TABLE bugsummary (
+    id integer NOT NULL,
+    count integer DEFAULT 0 NOT NULL,
+    product integer,
+    productseries integer,
+    distribution integer,
+    distroseries integer,
+    sourcepackagename integer,
+    viewed_by integer,
+    tag text,
+    status integer NOT NULL,
+    milestone integer,
+    importance integer NOT NULL,
+    has_patch boolean NOT NULL,
+    fixed_upstream boolean NOT NULL,
+    CONSTRAINT bugtask_assignment_checks CHECK (CASE WHEN (product IS NOT NULL) THEN ((((productseries IS NULL) AND (distribution IS NULL)) AND (distroseries IS NULL)) AND (sourcepackagename IS NULL)) WHEN (productseries IS NOT NULL) THEN (((distribution IS NULL) AND (distroseries IS NULL)) AND (sourcepackagename IS NULL)) WHEN (distribution IS NOT NULL) THEN (distroseries IS NULL) WHEN (distroseries IS NOT NULL) THEN true ELSE false END)
+);
+
+
+CREATE FUNCTION bug_summary_dec(bugsummary) RETURNS void
+    LANGUAGE sql
+    AS $_$
+    -- We own the row reference, so in the absence of bugs this cannot
+    -- fail - just decrement the row.
+    UPDATE BugSummary SET count = count + $1.count
+    WHERE
+        ((product IS NULL AND $1.product IS NULL)
+            OR product = $1.product)
+        AND ((productseries IS NULL AND $1.productseries IS NULL)
+            OR productseries = $1.productseries)
+        AND ((distribution IS NULL AND $1.distribution IS NULL)
+            OR distribution = $1.distribution)
+        AND ((distroseries IS NULL AND $1.distroseries IS NULL)
+            OR distroseries = $1.distroseries)
+        AND ((sourcepackagename IS NULL AND $1.sourcepackagename IS NULL)
+            OR sourcepackagename = $1.sourcepackagename)
+        AND ((viewed_by IS NULL AND $1.viewed_by IS NULL)
+            OR viewed_by = $1.viewed_by)
+        AND ((tag IS NULL AND $1.tag IS NULL)
+            OR tag = $1.tag)
+        AND status = $1.status
+        AND ((milestone IS NULL AND $1.milestone IS NULL)
+            OR milestone = $1.milestone)
+        AND importance = $1.importance
+        AND has_patch = $1.has_patch
+        AND fixed_upstream = $1.fixed_upstream;
+$_$;
+
+
+COMMENT ON FUNCTION bug_summary_dec(bugsummary) IS 'UPSERT into bugsummary incrementing one row';
+
+
+CREATE FUNCTION bug_summary_flush_temp_journal() RETURNS void
+    LANGUAGE plpgsql
+    AS $$
+DECLARE
+    d bugsummary%ROWTYPE;
+BEGIN
+    -- may get called even though no summaries were made (for simplicity in the
+    -- callers)
+    PERFORM ensure_bugsummary_temp_journal();
+    FOR d IN SELECT * FROM bugsummary_temp_journal LOOP
+        PERFORM bugsummary_journal_ins(d);
+    END LOOP;
+    TRUNCATE bugsummary_temp_journal;
+END;
+$$;
+
+
+COMMENT ON FUNCTION bug_summary_flush_temp_journal() IS 'flush the temporary bugsummary journal into the bugsummary table';
+
+
+CREATE FUNCTION bug_summary_inc(d bugsummary) RETURNS void
+    LANGUAGE plpgsql
+    AS $_$
+BEGIN
+    -- Shameless adaption from postgresql manual
+    LOOP
+        -- first try to update the row
+        UPDATE BugSummary SET count = count + d.count
+        WHERE
+            ((product IS NULL AND $1.product IS NULL)
+                OR product = $1.product)
+            AND ((productseries IS NULL AND $1.productseries IS NULL)
+                OR productseries = $1.productseries)
+            AND ((distribution IS NULL AND $1.distribution IS NULL)
+                OR distribution = $1.distribution)
+            AND ((distroseries IS NULL AND $1.distroseries IS NULL)
+                OR distroseries = $1.distroseries)
+            AND ((sourcepackagename IS NULL AND $1.sourcepackagename IS NULL)
+                OR sourcepackagename = $1.sourcepackagename)
+            AND ((viewed_by IS NULL AND $1.viewed_by IS NULL)
+                OR viewed_by = $1.viewed_by)
+            AND ((tag IS NULL AND $1.tag IS NULL)
+                OR tag = $1.tag)
+            AND status = $1.status
+            AND ((milestone IS NULL AND $1.milestone IS NULL)
+                OR milestone = $1.milestone)
+            AND importance = $1.importance
+            AND has_patch = $1.has_patch
+            AND fixed_upstream = $1.fixed_upstream;
+        IF found THEN
+            RETURN;
+        END IF;
+        -- not there, so try to insert the key
+        -- if someone else inserts the same key concurrently,
+        -- we could get a unique-key failure
+        BEGIN
+            INSERT INTO BugSummary(
+                count, product, productseries, distribution,
+                distroseries, sourcepackagename, viewed_by, tag,
+                status, milestone,
+                importance, has_patch, fixed_upstream)
+            VALUES (
+                d.count, d.product, d.productseries, d.distribution,
+                d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
+                d.status, d.milestone,
+                d.importance, d.has_patch, d.fixed_upstream);
+            RETURN;
+        EXCEPTION WHEN unique_violation THEN
+            -- do nothing, and loop to try the UPDATE again
+        END;
+    END LOOP;
+END;
+$_$;
+
+
+COMMENT ON FUNCTION bug_summary_inc(d bugsummary) IS 'UPSERT into bugsummary incrementing one row';
+
+
+CREATE FUNCTION bug_summary_temp_journal_ins(d bugsummary) RETURNS void
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    INSERT INTO BugSummary_Temp_Journal(
+        count, product, productseries, distribution,
+        distroseries, sourcepackagename, viewed_by, tag,
+        status, milestone, importance, has_patch, fixed_upstream)
+    VALUES (
+        d.count, d.product, d.productseries, d.distribution,
+        d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
+        d.status, d.milestone, d.importance, d.has_patch, d.fixed_upstream);
+    RETURN;
+END;
+$$;
+
+
+COMMENT ON FUNCTION bug_summary_temp_journal_ins(d bugsummary) IS 'Insert a BugSummary into the temporary journal';
+
+
+CREATE FUNCTION bug_update_heat_copy_to_bugtask() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF NEW.heat != OLD.heat THEN
+        UPDATE bugtask SET heat=NEW.heat WHERE bugtask.bug=NEW.id;
+    END IF;
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION bug_update_heat_copy_to_bugtask() IS 'Copies bug heat to bugtasks when the bug is changed. Runs on UPDATE only because INSERTs do not have bugtasks at the point of insertion.';
+
+
+CREATE FUNCTION bug_update_latest_patch_uploaded(integer) RETURNS void
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $_$
+BEGIN
+    UPDATE bug SET latest_patch_uploaded =
+        (SELECT max(message.datecreated)
+            FROM message, bugattachment
+            WHERE bugattachment.message=message.id AND
+                bugattachment.bug=$1 AND
+                bugattachment.type=1)
+        WHERE bug.id=$1;
+END;
+$_$;
+
+
+CREATE FUNCTION bug_update_latest_patch_uploaded_on_delete() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    PERFORM bug_update_latest_patch_uploaded(OLD.bug);
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+CREATE FUNCTION bug_update_latest_patch_uploaded_on_insert_update() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    PERFORM bug_update_latest_patch_uploaded(NEW.bug);
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+CREATE FUNCTION bugmessage_copy_owner_from_message() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        IF NEW.owner is NULL THEN
+            UPDATE BugMessage
+            SET owner = Message.owner FROM
+            Message WHERE
+            Message.id = NEW.message AND
+            BugMessage.id = NEW.id;
+        END IF;
+    ELSIF NEW.message != OLD.message THEN
+        UPDATE BugMessage
+        SET owner = Message.owner FROM
+        Message WHERE
+        Message.id = NEW.message AND
+        BugMessage.id = NEW.id;
+    END IF;
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION bugmessage_copy_owner_from_message() IS 'Copies the message owner into bugmessage when bugmessage changes.';
+
+
+CREATE FUNCTION bugsubscription_maintain_bug_summary() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    -- This trigger only works if we are inserting, updating or deleting
+    -- a single row per statement.
+    IF TG_OP = 'INSERT' THEN
+        IF NOT (bug_row(NEW.bug)).private THEN
+            -- Public subscriptions are not aggregated.
+            RETURN NEW;
+        END IF;
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(NEW.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(NEW.bug));
+        END IF;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN NEW;
+    ELSIF TG_OP = 'DELETE' THEN
+        IF NOT (bug_row(OLD.bug)).private THEN
+            -- Public subscriptions are not aggregated.
+            RETURN OLD;
+        END IF;
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(OLD.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(OLD.bug));
+        END IF;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN OLD;
+    ELSE
+        IF (OLD.person IS DISTINCT FROM NEW.person
+            OR OLD.bug IS DISTINCT FROM NEW.bug) THEN
+            IF TG_WHEN = 'BEFORE' THEN
+                IF (bug_row(OLD.bug)).private THEN
+                    -- Public subscriptions are not aggregated.
+                    PERFORM unsummarise_bug(bug_row(OLD.bug));
+                END IF;
+                IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN
+                    -- Public subscriptions are not aggregated.
+                    PERFORM unsummarise_bug(bug_row(NEW.bug));
+                END IF;
+            ELSE
+                IF (bug_row(OLD.bug)).private THEN
+                    -- Public subscriptions are not aggregated.
+                    PERFORM summarise_bug(bug_row(OLD.bug));
+                END IF;
+                IF OLD.bug <> NEW.bug AND (bug_row(NEW.bug)).private THEN
+                    -- Public subscriptions are not aggregated.
+                    PERFORM summarise_bug(bug_row(NEW.bug));
+                END IF;
+            END IF;
+        END IF;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN NEW;
+    END IF;
+END;
+$$;
+
+
+COMMENT ON FUNCTION bugsubscription_maintain_bug_summary() IS 'AFTER trigger on bugsubscription maintaining the bugs summaries in bugsummary.';
+
+
+CREATE FUNCTION bugsummary_journal_ins(d bugsummary) RETURNS void
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF d.count <> 0 THEN
+        INSERT INTO BugSummaryJournal (
+            count, product, productseries, distribution,
+            distroseries, sourcepackagename, viewed_by, tag,
+            status, milestone,
+            importance, has_patch, fixed_upstream)
+        VALUES (
+            d.count, d.product, d.productseries, d.distribution,
+            d.distroseries, d.sourcepackagename, d.viewed_by, d.tag,
+            d.status, d.milestone,
+            d.importance, d.has_patch, d.fixed_upstream);
+    END IF;
+END;
+$$;
+
+
+COMMENT ON FUNCTION bugsummary_journal_ins(d bugsummary) IS 'Add an entry into BugSummaryJournal';
+
+
+CREATE FUNCTION bugsummary_locations(bug_row bug) RETURNS SETOF bugsummary
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF BUG_ROW.duplicateof IS NOT NULL THEN
+        RETURN;
+    END IF;
+    RETURN QUERY
+        SELECT
+            CAST(NULL AS integer) AS id,
+            CAST(1 AS integer) AS count,
+            product, productseries, distribution, distroseries,
+            sourcepackagename, person AS viewed_by, tag, status, milestone,
+            importance,
+            BUG_ROW.latest_patch_uploaded IS NOT NULL AS has_patch,
+            (EXISTS (
+                SELECT TRUE FROM BugTask AS RBT
+                WHERE
+                    RBT.bug = tasks.bug
+                    -- This would just be 'RBT.id <> tasks.id', except
+                    -- that the records from tasks are summaries and not
+                    -- real bugtasks, and do not have an id.
+                    AND (RBT.product IS DISTINCT FROM tasks.product
+                        OR RBT.productseries
+                            IS DISTINCT FROM tasks.productseries
+                        OR RBT.distribution IS DISTINCT FROM tasks.distribution
+                        OR RBT.distroseries IS DISTINCT FROM tasks.distroseries
+                        OR RBT.sourcepackagename
+                            IS DISTINCT FROM tasks.sourcepackagename)
+                    -- Flagged as INVALID, FIXCOMMITTED or FIXRELEASED
+                    -- via a bugwatch, or FIXCOMMITTED or FIXRELEASED on
+                    -- the product.
+                    AND ((bugwatch IS NOT NULL AND status IN (17, 25, 30))
+                        OR (bugwatch IS NULL AND product IS NOT NULL
+                            AND status IN (25, 30))))
+                )::boolean AS fixed_upstream
+        FROM bugsummary_tasks(BUG_ROW) AS tasks
+        JOIN bugsummary_tags(BUG_ROW) AS bug_tags ON TRUE
+        LEFT OUTER JOIN bugsummary_viewers(BUG_ROW) AS bug_viewers ON TRUE;
+END;
+$$;
+
+
+COMMENT ON FUNCTION bugsummary_locations(bug_row bug) IS 'Calculate what BugSummary rows should exist for a given Bug.';
+
+
+CREATE FUNCTION bugsummary_rollup_journal(batchsize integer DEFAULT NULL::integer) RETURNS void
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+DECLARE
+    d bugsummary%ROWTYPE;
+    max_id integer;
+BEGIN
+    -- Lock so we don't content with other invokations of this
+    -- function. We can happily lock the BugSummary table for writes
+    -- as this function is the only thing that updates that table.
+    -- BugSummaryJournal remains unlocked so nothing should be blocked.
+    LOCK TABLE BugSummary IN ROW EXCLUSIVE MODE;
+
+    IF batchsize IS NULL THEN
+        SELECT MAX(id) INTO max_id FROM BugSummaryJournal;
+    ELSE
+        SELECT MAX(id) INTO max_id FROM (
+            SELECT id FROM BugSummaryJournal ORDER BY id LIMIT batchsize
+            ) AS Whatever;
+    END IF;
+
+    FOR d IN
+        SELECT
+            NULL as id,
+            SUM(count),
+            product,
+            productseries,
+            distribution,
+            distroseries,
+            sourcepackagename,
+            viewed_by,
+            tag,
+            status,
+            milestone,
+            importance,
+            has_patch,
+            fixed_upstream
+        FROM BugSummaryJournal
+        WHERE id <= max_id
+        GROUP BY
+            product, productseries, distribution, distroseries,
+            sourcepackagename, viewed_by, tag, status, milestone,
+            importance, has_patch, fixed_upstream
+        HAVING sum(count) <> 0
+    LOOP
+        IF d.count < 0 THEN
+            PERFORM bug_summary_dec(d);
+        ELSIF d.count > 0 THEN
+            PERFORM bug_summary_inc(d);
+        END IF;
+    END LOOP;
+
+    -- Clean out any counts we reduced to 0.
+    DELETE FROM BugSummary WHERE count=0;
+    -- Clean out the journal entries we have handled.
+    DELETE FROM BugSummaryJournal WHERE id <= max_id;
+END;
+$$;
+
+
+COMMENT ON FUNCTION bugsummary_rollup_journal(batchsize integer) IS 'Collate and migrate rows from BugSummaryJournal to BugSummary';
+
+
+CREATE FUNCTION valid_name(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    import re
+    name = args[0]
+    pat = r"^[a-z0-9][a-z0-9\+\.\-]*\Z"
+    if re.match(pat, name):
+        return 1
+    return 0
+$$;
+
+
+COMMENT ON FUNCTION valid_name(text) IS 'validate a name.
+
+    Names must contain only lowercase letters, numbers, ., & -. They
+    must start with an alphanumeric. They are ASCII only. Names are useful
+    for mneumonic identifiers such as nicknames and as URL components.
+    This specification is the same as the Debian product naming policy.
+
+    Note that a valid name might be all integers, so there is a possible
+    namespace conflict if URL traversal is possible by name as well as id.';
+
+
+CREATE TABLE bugtag (
+    id integer NOT NULL,
+    bug integer NOT NULL,
+    tag text NOT NULL,
+    CONSTRAINT valid_tag CHECK (valid_name(tag))
+);
+
+
+COMMENT ON TABLE bugtag IS 'Attaches simple text tags to a bug.';
+
+
+COMMENT ON COLUMN bugtag.bug IS 'The bug the tags is attached to.';
+
+
+COMMENT ON COLUMN bugtag.tag IS 'The text representation of the tag.';
+
+
+CREATE FUNCTION bugsummary_tags(bug_row bug) RETURNS SETOF bugtag
+    LANGUAGE sql STABLE
+    AS $_$
+    SELECT * FROM BugTag WHERE BugTag.bug = $1.id
+    UNION ALL
+    SELECT NULL::integer, $1.id, NULL::text;
+$_$;
+
+
+COMMENT ON FUNCTION bugsummary_tags(bug_row bug) IS 'Return (bug, tag) for all tags + (bug, NULL::text)';
+
+
+CREATE TABLE bugtask (
+    id integer NOT NULL,
+    bug integer NOT NULL,
+    product integer,
+    distribution integer,
+    distroseries integer,
+    sourcepackagename integer,
+    binarypackagename integer,
+    status integer NOT NULL,
+    importance integer DEFAULT 5 NOT NULL,
+    assignee integer,
+    date_assigned timestamp without time zone,
+    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone),
+    owner integer NOT NULL,
+    milestone integer,
+    bugwatch integer,
+    fti ts2.tsvector,
+    targetnamecache text,
+    date_confirmed timestamp without time zone,
+    date_inprogress timestamp without time zone,
+    date_closed timestamp without time zone,
+    productseries integer,
+    date_incomplete timestamp without time zone,
+    date_left_new timestamp without time zone,
+    date_triaged timestamp without time zone,
+    date_fix_committed timestamp without time zone,
+    date_fix_released timestamp without time zone,
+    date_left_closed timestamp without time zone,
+    heat_rank integer DEFAULT 0 NOT NULL,
+    date_milestone_set timestamp without time zone,
+    heat integer DEFAULT 0 NOT NULL,
+    CONSTRAINT bugtask_assignment_checks CHECK (CASE WHEN (product IS NOT NULL) THEN ((((productseries IS NULL) AND (distribution IS NULL)) AND (distroseries IS NULL)) AND (sourcepackagename IS NULL)) WHEN (productseries IS NOT NULL) THEN (((distribution IS NULL) AND (distroseries IS NULL)) AND (sourcepackagename IS NULL)) WHEN (distribution IS NOT NULL) THEN (distroseries IS NULL) WHEN (distroseries IS NOT NULL) THEN true ELSE false END)
+);
+
+
+COMMENT ON TABLE bugtask IS 'Links a given Bug to a particular (sourcepackagename, distro) or product.';
+
+
+COMMENT ON COLUMN bugtask.bug IS 'The bug that is assigned to this (sourcepackagename, distro) or product.';
+
+
+COMMENT ON COLUMN bugtask.product IS 'The product in which this bug shows up.';
+
+
+COMMENT ON COLUMN bugtask.distribution IS 'The distro of the named sourcepackage.';
+
+
+COMMENT ON COLUMN bugtask.sourcepackagename IS 'The name of the sourcepackage in which this bug shows up.';
+
+
+COMMENT ON COLUMN bugtask.binarypackagename IS 'The name of the binary package built from the source package. This column may only contain a value if this bug task is linked to a sourcepackage (not a product)';
+
+
+COMMENT ON COLUMN bugtask.status IS 'The general health of the bug, e.g. Accepted, Rejected, etc.';
+
+
+COMMENT ON COLUMN bugtask.importance IS 'The importance of fixing the bug.';
+
+
+COMMENT ON COLUMN bugtask.assignee IS 'The person who has been assigned to fix this bug in this product or (sourcepackagename, distro)';
+
+
+COMMENT ON COLUMN bugtask.date_assigned IS 'The date on which the bug in this (sourcepackagename, distro) or product was assigned to someone to fix';
+
+
+COMMENT ON COLUMN bugtask.datecreated IS 'A timestamp for the creation of this bug assignment. Note that this is not the date the bug was created (though it might be), it''s the date the bug was assigned to this product, which could have come later.';
+
+
+COMMENT ON COLUMN bugtask.milestone IS 'A way to mark a bug for grouping purposes, e.g. to say it needs to be fixed by version 1.2';
+
+
+COMMENT ON COLUMN bugtask.bugwatch IS 'This column allows us to link a bug
+task to a bug watch. In other words, we are connecting the state of the task
+to the state of the bug in a different bug tracking system. To the best of
+our ability we''ll try and keep the bug task syncronised with the state of
+the remote bug watch.';
+
+
+COMMENT ON COLUMN bugtask.targetnamecache IS 'A cached value of the target name of this bugtask, to make it easier to sort and search on the target name.';
+
+
+COMMENT ON COLUMN bugtask.date_confirmed IS 'The date when this bug transitioned from an unconfirmed status to a confirmed one. If the state regresses to a one that logically occurs before Confirmed, e.g., Unconfirmed, this date is cleared.';
+
+
+COMMENT ON COLUMN bugtask.date_inprogress IS 'The date on which this bug transitioned from not being in progress to a state >= In Progress. If the status moves back to a pre-In Progress state, this date is cleared';
+
+
+COMMENT ON COLUMN bugtask.date_closed IS 'The date when this bug transitioned to a resolved state, e.g., Rejected, Fix Released, etc. If the state changes back to a pre-closed state, this date is cleared';
+
+
+COMMENT ON COLUMN bugtask.productseries IS 'The product series to which the bug is targeted';
+
+
+COMMENT ON COLUMN bugtask.date_left_new IS 'The date when this bug first transitioned out of the NEW status.';
+
+
+COMMENT ON COLUMN bugtask.date_triaged IS 'The date when this bug transitioned to a status >= TRIAGED.';
+
+
+COMMENT ON COLUMN bugtask.date_fix_committed IS 'The date when this bug transitioned to a status >= FIXCOMMITTED.';
+
+
+COMMENT ON COLUMN bugtask.date_fix_released IS 'The date when this bug transitioned to a FIXRELEASED status.';
+
+
+COMMENT ON COLUMN bugtask.date_left_closed IS 'The date when this bug last transitioned out of a CLOSED status.';
+
+
+COMMENT ON COLUMN bugtask.heat_rank IS 'The heat bin in which this bugtask appears, as a value from the BugTaskHeatRank enumeration.';
+
+
+COMMENT ON COLUMN bugtask.date_milestone_set IS 'The date when this bug was targed to the milestone that is currently set.';
+
+
+CREATE FUNCTION bugsummary_tasks(bug_row bug) RETURNS SETOF bugtask
+    LANGUAGE plpgsql STABLE
+    AS $$
+DECLARE
+    bt bugtask%ROWTYPE;
+    r record;
+BEGIN
+    bt.bug = BUG_ROW.id;
+
+    -- One row only for each target permutation - need to ignore other fields
+    -- like date last modified to deal with conjoined masters and multiple
+    -- sourcepackage tasks in a distro.
+    FOR r IN
+        SELECT
+            product, productseries, distribution, distroseries,
+            sourcepackagename, status, milestone, importance, bugwatch
+        FROM BugTask WHERE bug=BUG_ROW.id
+        UNION -- Implicit DISTINCT
+        SELECT
+            product, productseries, distribution, distroseries,
+            NULL, status, milestone, importance, bugwatch
+        FROM BugTask WHERE bug=BUG_ROW.id AND sourcepackagename IS NOT NULL
+    LOOP
+        bt.product = r.product;
+        bt.productseries = r.productseries;
+        bt.distribution = r.distribution;
+        bt.distroseries = r.distroseries;
+        bt.sourcepackagename = r.sourcepackagename;
+        bt.status = r.status;
+        bt.milestone = r.milestone;
+        bt.importance = r.importance;
+        bt.bugwatch = r.bugwatch;
+        RETURN NEXT bt;
+    END LOOP;
+END;
+$$;
+
+
+COMMENT ON FUNCTION bugsummary_tasks(bug_row bug) IS 'Return all tasks for the bug + all sourcepackagename tasks again with the sourcepackagename squashed';
+
+
+CREATE TABLE bugsubscription (
+    id integer NOT NULL,
+    person integer NOT NULL,
+    bug integer NOT NULL,
+    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
+    subscribed_by integer NOT NULL,
+    bug_notification_level integer DEFAULT 40 NOT NULL
+);
+
+
+COMMENT ON TABLE bugsubscription IS 'A subscription by a Person to a bug.';
+
+
+COMMENT ON COLUMN bugsubscription.bug_notification_level IS 'The level of notifications which the Person will receive from this subscription.';
+
+
+CREATE FUNCTION bugsummary_viewers(bug_row bug) RETURNS SETOF bugsubscription
+    LANGUAGE sql STABLE
+    AS $_$
+    SELECT *
+    FROM BugSubscription
+    WHERE
+        bugsubscription.bug=$1.id
+        AND $1.private IS TRUE;
+$_$;
+
+
+COMMENT ON FUNCTION bugsummary_viewers(bug_row bug) IS 'Return (bug, viewer) for all viewers if private, nothing otherwise';
+
+
+CREATE FUNCTION bugtag_maintain_bug_summary() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(NEW.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(NEW.bug));
+        END IF;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN NEW;
+    ELSIF TG_OP = 'DELETE' THEN
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(OLD.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(OLD.bug));
+        END IF;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN OLD;
+    ELSE
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(OLD.bug));
+            IF OLD.bug <> NEW.bug THEN
+                PERFORM unsummarise_bug(bug_row(NEW.bug));
+            END IF;
+        ELSE
+            PERFORM summarise_bug(bug_row(OLD.bug));
+            IF OLD.bug <> NEW.bug THEN
+                PERFORM summarise_bug(bug_row(NEW.bug));
+            END IF;
+        END IF;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN NEW;
+    END IF;
+END;
+$$;
+
+
+COMMENT ON FUNCTION bugtag_maintain_bug_summary() IS 'AFTER trigger on bugtag maintaining the bugs summaries in bugsummary.';
+
+
+CREATE FUNCTION bugtask_maintain_bug_summary() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    -- This trigger only works if we are inserting, updating or deleting
+    -- a single row per statement.
+
+    -- Unlike bug_maintain_bug_summary, this trigger does not have access
+    -- to the old bug when invoked as an AFTER trigger. To work around this
+    -- we install this trigger as both a BEFORE and an AFTER trigger.
+    IF TG_OP = 'INSERT' THEN
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(NEW.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(NEW.bug));
+        END IF;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN NEW;
+
+    ELSIF TG_OP = 'DELETE' THEN
+        IF TG_WHEN = 'BEFORE' THEN
+            PERFORM unsummarise_bug(bug_row(OLD.bug));
+        ELSE
+            PERFORM summarise_bug(bug_row(OLD.bug));
+        END IF;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN OLD;
+
+    ELSE
+        IF (OLD.product IS DISTINCT FROM NEW.product
+            OR OLD.productseries IS DISTINCT FROM NEW.productseries
+            OR OLD.distribution IS DISTINCT FROM NEW.distribution
+            OR OLD.distroseries IS DISTINCT FROM NEW.distroseries
+            OR OLD.sourcepackagename IS DISTINCT FROM NEW.sourcepackagename
+            OR OLD.status IS DISTINCT FROM NEW.status
+            OR OLD.importance IS DISTINCT FROM NEW.importance
+            OR OLD.bugwatch IS DISTINCT FROM NEW.bugwatch
+            OR OLD.milestone IS DISTINCT FROM NEW.milestone) THEN
+
+            IF TG_WHEN = 'BEFORE' THEN
+                PERFORM unsummarise_bug(bug_row(OLD.bug));
+                IF OLD.bug <> NEW.bug THEN
+                    PERFORM unsummarise_bug(bug_row(NEW.bug));
+                END IF;
+            ELSE
+                PERFORM summarise_bug(bug_row(OLD.bug));
+                IF OLD.bug <> NEW.bug THEN
+                    PERFORM summarise_bug(bug_row(NEW.bug));
+                END IF;
+            END IF;
+        END IF;
+        PERFORM bug_summary_flush_temp_journal();
+        RETURN NEW;
+    END IF;
+END;
+$$;
+
+
+COMMENT ON FUNCTION bugtask_maintain_bug_summary() IS 'Both BEFORE & AFTER trigger on bugtask maintaining the bugs summaries in bugsummary.';
+
+
+CREATE FUNCTION calculate_bug_heat(bug_id integer) RETURNS integer
+    LANGUAGE plpythonu STABLE STRICT
+    AS $$
+    from datetime import datetime
+
+    class BugHeatConstants:
+        PRIVACY = 150
+        SECURITY = 250
+        DUPLICATE = 6
+        AFFECTED_USER = 4
+        SUBSCRIBER = 2
+
+    def get_max_heat_for_bug(bug_id):
+        results = plpy.execute("""
+            SELECT MAX(
+                GREATEST(Product.max_bug_heat,
+                         DistributionSourcePackage.max_bug_heat))
+                    AS max_heat
+            FROM BugTask
+            LEFT OUTER JOIN ProductSeries ON
+                BugTask.productseries = ProductSeries.id
+            LEFT OUTER JOIN Product ON (
+                BugTask.product = Product.id
+                OR ProductSeries.product = Product.id)
+            LEFT OUTER JOIN DistroSeries ON
+                BugTask.distroseries = DistroSeries.id
+            LEFT OUTER JOIN Distribution ON (
+                BugTask.distribution = Distribution.id
+                OR DistroSeries.distribution = Distribution.id)
+            LEFT OUTER JOIN DistributionSourcePackage ON (
+                BugTask.sourcepackagename =
+                    DistributionSourcePackage.sourcepackagename)
+            WHERE
+                BugTask.bug = %s""" % bug_id)
+
+        return results[0]['max_heat']
+
+    # It would be nice to be able to just SELECT * here, but we need the
+    # timestamps to be in a format that datetime.fromtimestamp() will
+    # understand.
+    bug_data = plpy.execute("""
+        SELECT
+            duplicateof,
+            private,
+            security_related,
+            number_of_duplicates,
+            users_affected_count,
+            EXTRACT(epoch from datecreated)
+                AS timestamp_date_created,
+            EXTRACT(epoch from date_last_updated)
+                AS timestamp_date_last_updated,
+            EXTRACT(epoch from date_last_message)
+                AS timestamp_date_last_message
+        FROM Bug WHERE id = %s""" % bug_id)
+
+    if bug_data.nrows() == 0:
+        raise Exception("Bug %s doesn't exist." % bug_id)
+
+    bug = bug_data[0]
+    if bug['duplicateof'] is not None:
+        return None
+
+    heat = {}
+    heat['dupes'] = (
+        BugHeatConstants.DUPLICATE * bug['number_of_duplicates'])
+    heat['affected_users'] = (
+        BugHeatConstants.AFFECTED_USER *
+        bug['users_affected_count'])
+
+    if bug['private']:
+        heat['privacy'] = BugHeatConstants.PRIVACY
+    if bug['security_related']:
+        heat['security'] = BugHeatConstants.SECURITY
+
+    # Get the heat from subscribers, both direct and via duplicates.
+    subs_from_dupes = plpy.execute("""
+        SELECT COUNT(DISTINCT BugSubscription.person) AS sub_count
+        FROM BugSubscription, Bug
+        WHERE Bug.id = BugSubscription.bug
+            AND (Bug.id = %s OR Bug.duplicateof = %s)"""
+        % (bug_id, bug_id))
+
+    heat['subcribers'] = (
+        BugHeatConstants.SUBSCRIBER
+        * subs_from_dupes[0]['sub_count'])
+
+    total_heat = sum(heat.values())
+
+    # Bugs decay over time. Every day the bug isn't touched its heat
+    # decreases by 1%.
+    date_last_updated = datetime.fromtimestamp(
+        bug['timestamp_date_last_updated'])
+    days_since_last_update = (datetime.utcnow() - date_last_updated).days
+    total_heat = int(total_heat * (0.99 ** days_since_last_update))
+
+    if days_since_last_update > 0:
+        # Bug heat increases by a quarter of the maximum bug heat
+        # divided by the number of days since the bug's creation date.
+        date_created = datetime.fromtimestamp(
+            bug['timestamp_date_created'])
+
+        if bug['timestamp_date_last_message'] is not None:
+            date_last_message = datetime.fromtimestamp(
+                bug['timestamp_date_last_message'])
+            oldest_date = max(date_last_updated, date_last_message)
+        else:
+            date_last_message = None
+            oldest_date = date_last_updated
+
+        days_since_last_activity = (datetime.utcnow() - oldest_date).days
+        days_since_created = (datetime.utcnow() - date_created).days
+        max_heat = get_max_heat_for_bug(bug_id)
+        if max_heat is not None and days_since_created > 0:
+            total_heat = (
+                total_heat + (max_heat * 0.25 / days_since_created))
+
+    return int(total_heat)
+$$;
+
+
+CREATE FUNCTION cursor_fetch(cur refcursor, n integer) RETURNS SETOF record
+    LANGUAGE plpgsql
+    AS $$
+DECLARE
+    r record;
+    count integer;
+BEGIN
+    FOR count IN 1..n LOOP
+        FETCH FORWARD FROM cur INTO r;
+        IF NOT FOUND THEN
+            RETURN;
+        END IF;
+        RETURN NEXT r;
+    END LOOP;
+END;
+$$;
+
+
+COMMENT ON FUNCTION cursor_fetch(cur refcursor, n integer) IS 'Fetch the next n items from a cursor. Work around for not being able to use FETCH inside a SELECT statement.';
+
+
+CREATE FUNCTION debversion(character) RETURNS debversion
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$rtrim1$$;
+
+
+CREATE FUNCTION debversion_cmp(version1 debversion, version2 debversion) RETURNS integer
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_cmp';
+
+
+COMMENT ON FUNCTION debversion_cmp(version1 debversion, version2 debversion) IS 'Compare Debian versions';
+
+
+CREATE FUNCTION debversion_eq(version1 debversion, version2 debversion) RETURNS boolean
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_eq';
+
+
+COMMENT ON FUNCTION debversion_eq(version1 debversion, version2 debversion) IS 'debversion equal';
+
+
+CREATE FUNCTION debversion_ge(version1 debversion, version2 debversion) RETURNS boolean
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_ge';
+
+
+COMMENT ON FUNCTION debversion_ge(version1 debversion, version2 debversion) IS 'debversion greater-than-or-equal';
+
+
+CREATE FUNCTION debversion_gt(version1 debversion, version2 debversion) RETURNS boolean
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_gt';
+
+
+COMMENT ON FUNCTION debversion_gt(version1 debversion, version2 debversion) IS 'debversion greater-than';
+
+
+CREATE FUNCTION debversion_hash(debversion) RETURNS integer
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_hash';
+
+
+CREATE FUNCTION debversion_larger(version1 debversion, version2 debversion) RETURNS debversion
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_larger';
+
+
+CREATE FUNCTION debversion_le(version1 debversion, version2 debversion) RETURNS boolean
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_le';
+
+
+COMMENT ON FUNCTION debversion_le(version1 debversion, version2 debversion) IS 'debversion less-than-or-equal';
+
+
+CREATE FUNCTION debversion_lt(version1 debversion, version2 debversion) RETURNS boolean
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_lt';
+
+
+COMMENT ON FUNCTION debversion_lt(version1 debversion, version2 debversion) IS 'debversion less-than';
+
+
+CREATE FUNCTION debversion_ne(version1 debversion, version2 debversion) RETURNS boolean
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_ne';
+
+
+COMMENT ON FUNCTION debversion_ne(version1 debversion, version2 debversion) IS 'debversion not equal';
+
+
+CREATE FUNCTION debversion_smaller(version1 debversion, version2 debversion) RETURNS debversion
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/debversion', 'debversion_smaller';
+
+
+CREATE FUNCTION debversion_sort_key(version text) RETURNS text
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $_$
+    # If this method is altered, then any functional indexes using it
+    # need to be rebuilt.
+    import re
+
+    VERRE = re.compile("(?:([0-9]+):)?(.+?)(?:-([^-]+))?$")
+
+    MAP = "0123456789ABCDEFGHIJKLMNOPQRSTUV"
+
+    epoch, version, release = VERRE.match(args[0]).groups()
+    key = []
+    for part, part_weight in ((epoch, 3000), (version, 2000), (release, 1000)):
+        if not part:
+            continue
+        i = 0
+        l = len(part)
+        while i != l:
+            c = part[i]
+            if c.isdigit():
+                key.append(part_weight)
+                j = i
+                while i != l and part[i].isdigit(): i += 1
+                key.append(part_weight+int(part[j:i] or "0"))
+            elif c == "~":
+                key.append(0)
+                i += 1
+            elif c.isalpha():
+                key.append(part_weight+ord(c))
+                i += 1
+            else:
+                key.append(part_weight+256+ord(c))
+                i += 1
+        if not key or key[-1] != part_weight:
+            key.append(part_weight)
+            key.append(part_weight)
+    key.append(1)
+
+    # Encode our key and return it
+    #
+    result = []
+    for value in key:
+        if not value:
+            result.append("000")
+        else:
+            element = []
+            while value:
+                element.insert(0, MAP[value & 0x1F])
+                value >>= 5
+            element_len = len(element)
+            if element_len < 3:
+                element.insert(0, "0"*(3-element_len))
+            elif element_len == 3:
+                pass
+            elif element_len < 35:
+                element.insert(0, MAP[element_len-4])
+                element.insert(0, "X")
+            elif element_len < 1027:
+                element.insert(0, MAP[(element_len-4) & 0x1F])
+                element.insert(0, MAP[(element_len-4) & 0x3E0])
+                element.insert(0, "Y")
+            else:
+                raise ValueError("Number too large")
+            result.extend(element)
+    return "".join(result)
+$_$;
+
+
+COMMENT ON FUNCTION debversion_sort_key(version text) IS 'Return a string suitable for sorting debian version strings on';
+
+
+CREATE FUNCTION ensure_bugsummary_temp_journal() RETURNS void
+    LANGUAGE plpgsql
+    AS $$
+DECLARE
+BEGIN
+    CREATE TEMPORARY TABLE bugsummary_temp_journal (
+        LIKE bugsummary ) ON COMMIT DROP;
+    ALTER TABLE bugsummary_temp_journal ALTER COLUMN id DROP NOT NULL;
+EXCEPTION
+    WHEN duplicate_table THEN
+        NULL;
+END;
+$$;
+
+
+COMMENT ON FUNCTION ensure_bugsummary_temp_journal() IS 'Create a temporary table bugsummary_temp_journal if it does not exist.';
+
+
+CREATE FUNCTION generate_openid_identifier() RETURNS text
+    LANGUAGE plpythonu
+    AS $$
+    from random import choice
+
+    # Non display confusing characters.
+    chars = '34678bcdefhkmnprstwxyzABCDEFGHJKLMNPQRTWXY'
+
+    # Character length of tokens. Can be increased, decreased or even made
+    # random - Launchpad does not care. 7 means it takes 40 bytes to store
+    # a null-terminated Launchpad identity URL on the current domain name.
+    length=7
+
+    loop_count = 0
+    while loop_count < 20000:
+        # Generate a random openid_identifier
+        oid = ''.join(choice(chars) for count in range(length))
+
+        # Check if the oid is already in the db, although this is pretty
+        # unlikely
+        rv = plpy.execute("""
+            SELECT COUNT(*) AS num FROM Account WHERE openid_identifier = '%s'
+            """ % oid, 1)
+        if rv[0]['num'] == 0:
+            return oid
+        loop_count += 1
+        if loop_count == 1:
+            plpy.warning(
+                'Clash generating unique openid_identifier. '
+                'Increase length if you see this warning too much.')
+    plpy.error(
+        "Unable to generate unique openid_identifier. "
+        "Need to increase length of tokens.")
+$$;
+
+
+CREATE FUNCTION getlocalnodeid() RETURNS integer
+    LANGUAGE plpgsql STABLE SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+    DECLARE
+        v_node_id integer;
+    BEGIN
+        SELECT INTO v_node_id _sl.getlocalnodeid('_sl');
+        RETURN v_node_id;
+    EXCEPTION
+        WHEN invalid_schema_name THEN
+            RETURN NULL;
+    END;
+$$;
+
+
+COMMENT ON FUNCTION getlocalnodeid() IS 'Return the replication node id for this node, or NULL if not a replicated installation.';
+
+
+CREATE FUNCTION is_blacklisted_name(text, integer) RETURNS boolean
+    LANGUAGE sql STABLE STRICT SECURITY DEFINER
+    SET search_path TO public
+    AS $_$
+    SELECT COALESCE(name_blacklist_match($1, $2)::boolean, FALSE);
+$_$;
+
+
+COMMENT ON FUNCTION is_blacklisted_name(text, integer) IS 'Return TRUE if any regular expressions stored in the NameBlacklist table match the givenname, otherwise return FALSE.';
+
+
+CREATE FUNCTION is_person(text) RETURNS boolean
+    LANGUAGE sql STABLE STRICT
+    AS $_$
+    SELECT count(*)>0 FROM Person WHERE name=$1 AND teamowner IS NULL;
+$_$;
+
+
+COMMENT ON FUNCTION is_person(text) IS 'True if the given name identifies a person in the Person table';
+
+
+CREATE FUNCTION is_printable_ascii(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $_$
+    import re, string
+    try:
+        text = args[0].decode("ASCII")
+    except UnicodeError:
+        return False
+    if re.search(r"^[%s]*$" % re.escape(string.printable), text) is None:
+        return False
+    return True
+$_$;
+
+
+COMMENT ON FUNCTION is_printable_ascii(text) IS 'True if the string is pure printable US-ASCII';
+
+
+CREATE FUNCTION is_team(integer) RETURNS boolean
+    LANGUAGE sql STABLE STRICT
+    AS $_$
+    SELECT count(*)>0 FROM Person WHERE id=$1 AND teamowner IS NOT NULL;
+$_$;
+
+
+COMMENT ON FUNCTION is_team(integer) IS 'True if the given id identifies a team in the Person table';
+
+
+CREATE FUNCTION is_team(text) RETURNS boolean
+    LANGUAGE sql STABLE STRICT
+    AS $_$
+    SELECT count(*)>0 FROM Person WHERE name=$1 AND teamowner IS NOT NULL;
+$_$;
+
+
+COMMENT ON FUNCTION is_team(text) IS 'True if the given name identifies a team in the Person table';
+
+
+CREATE FUNCTION lp_mirror_account_ins() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    INSERT INTO lp_Account (id, openid_identifier)
+    VALUES (NEW.id, NEW.openid_identifier);
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_account_upd() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF OLD.id <> NEW.id OR OLD.openid_identifier <> NEW.openid_identifier THEN
+        UPDATE lp_Account
+        SET id = NEW.id, openid_identifier = NEW.openid_identifier
+        WHERE id = OLD.id;
+    END IF;
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_del() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    EXECUTE 'DELETE FROM lp_' || TG_TABLE_NAME || ' WHERE id=' || OLD.id;
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_openididentifier_del() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+DECLARE
+    next_identifier text;
+BEGIN
+    SELECT INTO next_identifier identifier FROM OpenIdIdentifier
+    WHERE account = OLD.account AND identifier <> OLD.identifier
+    ORDER BY date_created DESC LIMIT 1;
+
+    IF next_identifier IS NOT NULL THEN
+        UPDATE lp_account SET openid_identifier = next_identifier
+        WHERE openid_identifier = OLD.identifier;
+    ELSE
+        DELETE FROM lp_account WHERE openid_identifier = OLD.identifier;
+    END IF;
+
+    DELETE FROM lp_OpenIdIdentifier WHERE identifier = OLD.identifier;
+
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_openididentifier_ins() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    -- Support obsolete lp_Account.openid_identifier as best we can
+    -- until ISD migrates to using lp_OpenIdIdentifier.
+    UPDATE lp_account SET openid_identifier = NEW.identifier
+    WHERE id = NEW.account;
+    IF NOT found THEN
+        INSERT INTO lp_account (id, openid_identifier)
+        VALUES (NEW.account, NEW.identifier);
+    END IF;
+
+    INSERT INTO lp_OpenIdIdentifier (identifier, account, date_created)
+    VALUES (NEW.identifier, NEW.account, NEW.date_created);
+
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_openididentifier_upd() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF OLD.identifier <> NEW.identifier THEN
+        UPDATE lp_Account SET openid_identifier = NEW.identifier
+        WHERE openid_identifier = OLD.identifier;
+    END IF;
+    UPDATE lp_OpenIdIdentifier
+    SET
+        identifier = NEW.identifier,
+        account = NEW.account,
+        date_created = NEW.date_created
+    WHERE identifier = OLD.identifier;
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_person_ins() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    INSERT INTO lp_Person (
+        id, displayname, teamowner, teamdescription, name, language, fti,
+        defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy,
+        merged, datecreated, homepage_content, icon, mugshot,
+        hide_email_addresses, creation_rationale, creation_comment,
+        registrant, logo, renewal_policy, personal_standing,
+        personal_standing_reason, mail_resumption_date,
+        mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates,
+        visibility, verbose_bugnotifications, account)
+    VALUES (
+        NEW.id, NEW.displayname, NEW.teamowner, NULL,
+        NEW.name, NEW.language, NEW.fti, NEW.defaultmembershipperiod,
+        NEW.defaultrenewalperiod, NEW.subscriptionpolicy,
+        NEW.merged, NEW.datecreated, NULL, NEW.icon,
+        NEW.mugshot, NEW.hide_email_addresses, NEW.creation_rationale,
+        NEW.creation_comment, NEW.registrant, NEW.logo, NEW.renewal_policy,
+        NEW.personal_standing, NEW.personal_standing_reason,
+        NEW.mail_resumption_date, NEW.mailing_list_auto_subscribe_policy,
+        NEW.mailing_list_receive_duplicates, NEW.visibility,
+        NEW.verbose_bugnotifications, NEW.account);
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_person_upd() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    UPDATE lp_Person
+    SET id = NEW.id,
+        displayname = NEW.displayname,
+        teamowner = NEW.teamowner,
+        teamdescription = NULL,
+        name = NEW.name,
+        language = NEW.language,
+        fti = NEW.fti,
+        defaultmembershipperiod = NEW.defaultmembershipperiod,
+        defaultrenewalperiod = NEW.defaultrenewalperiod,
+        subscriptionpolicy = NEW.subscriptionpolicy,
+        merged = NEW.merged,
+        datecreated = NEW.datecreated,
+        homepage_content = NULL,
+        icon = NEW.icon,
+        mugshot = NEW.mugshot,
+        hide_email_addresses = NEW.hide_email_addresses,
+        creation_rationale = NEW.creation_rationale,
+        creation_comment = NEW.creation_comment,
+        registrant = NEW.registrant,
+        logo = NEW.logo,
+        renewal_policy = NEW.renewal_policy,
+        personal_standing = NEW.personal_standing,
+        personal_standing_reason = NEW.personal_standing_reason,
+        mail_resumption_date = NEW.mail_resumption_date,
+        mailing_list_auto_subscribe_policy
+            = NEW.mailing_list_auto_subscribe_policy,
+        mailing_list_receive_duplicates = NEW.mailing_list_receive_duplicates,
+        visibility = NEW.visibility,
+        verbose_bugnotifications = NEW.verbose_bugnotifications,
+        account = NEW.account
+    WHERE id = OLD.id;
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_personlocation_ins() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    INSERT INTO lp_PersonLocation SELECT NEW.*;
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_personlocation_upd() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    UPDATE lp_PersonLocation
+    SET id = NEW.id,
+        date_created = NEW.date_created,
+        person = NEW.person,
+        latitude = NEW.latitude,
+        longitude = NEW.longitude,
+        time_zone = NEW.time_zone,
+        last_modified_by = NEW.last_modified_by,
+        date_last_modified = NEW.date_last_modified,
+        visible = NEW.visible,
+        locked = NEW.locked
+    WHERE id = OLD.id;
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_teamparticipation_ins() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    INSERT INTO lp_TeamParticipation SELECT NEW.*;
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION lp_mirror_teamparticipation_upd() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    UPDATE lp_TeamParticipation
+    SET id = NEW.id,
+        team = NEW.team,
+        person = NEW.person
+    WHERE id = OLD.id;
+    RETURN NULL; -- Ignored for AFTER triggers.
+END;
+$$;
+
+
+CREATE FUNCTION maintain_transitively_private() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF TG_OP = 'UPDATE' THEN
+        IF (NEW.stacked_on IS NOT DISTINCT FROM OLD.stacked_on
+            AND NEW.private IS NOT DISTINCT FROM OLD.private) THEN
+            RETURN NULL;
+        END IF;
+    END IF;
+    PERFORM update_transitively_private(NEW.id);
+    RETURN NULL;
+END;
+$$;
+
+
+COMMENT ON FUNCTION maintain_transitively_private() IS 'Trigger maintaining the Branch transitively_private column';
+
+
+CREATE FUNCTION message_copy_owner_to_bugmessage() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF NEW.owner != OLD.owner THEN
+        UPDATE BugMessage
+        SET owner = NEW.owner
+        WHERE
+        BugMessage.message = NEW.id;
+    END IF;
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION message_copy_owner_to_bugmessage() IS 'Copies the message owner into bugmessage when message changes.';
+
+
+CREATE FUNCTION message_copy_owner_to_questionmessage() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF NEW.owner != OLD.owner THEN
+        UPDATE QuestionMessage
+        SET owner = NEW.owner
+        WHERE
+        QuestionMessage.message = NEW.id;
+    END IF;
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION message_copy_owner_to_questionmessage() IS 'Copies the message owner into questionmessage when message changes.';
+
+
+CREATE FUNCTION milestone_sort_key(dateexpected timestamp without time zone, name text) RETURNS text
+    LANGUAGE plpythonu IMMUTABLE
+    AS $$
+    # If this method is altered, then any functional indexes using it
+    # need to be rebuilt.
+    import re
+    import datetime
+
+    date_expected, name = args
+
+    def substitute_filled_numbers(match):
+        return match.group(0).zfill(5)
+
+    name = re.sub(u'\d+', substitute_filled_numbers, name)
+    if date_expected is None:
+        # NULL dates are considered to be in the future.
+        date_expected = datetime.datetime(datetime.MAXYEAR, 1, 1)
+    return '%s %s' % (date_expected, name)
+$$;
+
+
+COMMENT ON FUNCTION milestone_sort_key(dateexpected timestamp without time zone, name text) IS 'Sort by the Milestone dateexpected and name. If the dateexpected is NULL, then it is converted to a date far in the future, so it will be sorted as a milestone in the future.';
+
+
+CREATE FUNCTION mv_branch_distribution_update() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF OLD.id != NEW.id THEN
+        RAISE EXCEPTION 'Cannot change Distribution.id';
+    END IF;
+    IF OLD.name != NEW.name THEN
+        UPDATE Branch SET unique_name = NULL
+        FROM DistroSeries
+        WHERE Branch.distroseries = Distroseries.id
+            AND Distroseries.distribution = NEW.id;
+    END IF;
+    RETURN NULL;
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_branch_distribution_update() IS 'Maintain Branch name cache when Distribution is modified.';
+
+
+CREATE FUNCTION mv_branch_distroseries_update() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF OLD.id != NEW.id THEN
+        RAISE EXCEPTION 'Cannot change Distroseries.id';
+    END IF;
+    IF OLD.name != NEW.name THEN
+        UPDATE Branch SET unique_name = NULL
+        WHERE Branch.distroseries = NEW.id;
+    END IF;
+    RETURN NULL;
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_branch_distroseries_update() IS 'Maintain Branch name cache when Distroseries is modified.';
+
+
+CREATE FUNCTION mv_branch_person_update() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+DECLARE
+    v_branch RECORD;
+BEGIN
+    IF OLD.id != NEW.id THEN
+        RAISE EXCEPTION 'Cannot change Person.id';
+    END IF;
+    IF OLD.name != NEW.name THEN
+        UPDATE Branch SET owner_name = NEW.name WHERE owner = NEW.id;
+    END IF;
+    RETURN NULL;
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_branch_person_update() IS 'Maintain Branch name cache when Person is modified.';
+
+
+CREATE FUNCTION mv_branch_product_update() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+DECLARE
+    v_branch RECORD;
+BEGIN
+    IF OLD.id != NEW.id THEN
+        RAISE EXCEPTION 'Cannot change Product.id';
+    END IF;
+    IF OLD.name != NEW.name THEN
+        UPDATE Branch SET target_suffix = NEW.name WHERE product=NEW.id;
+    END IF;
+    RETURN NULL;
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_branch_product_update() IS 'Maintain Branch name cache when Product is modified.';
+
+
+CREATE FUNCTION mv_pillarname_distribution() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        INSERT INTO PillarName (name, distribution)
+        VALUES (NEW.name, NEW.id);
+    ELSIF NEW.name != OLD.name THEN
+        UPDATE PillarName SET name=NEW.name WHERE distribution=NEW.id;
+    END IF;
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_pillarname_distribution() IS 'Trigger maintaining the PillarName table';
+
+
+CREATE FUNCTION mv_pillarname_product() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        INSERT INTO PillarName (name, product, active)
+        VALUES (NEW.name, NEW.id, NEW.active);
+    ELSIF NEW.name != OLD.name OR NEW.active != OLD.active THEN
+        UPDATE PillarName SET name=NEW.name, active=NEW.active
+        WHERE product=NEW.id;
+    END IF;
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_pillarname_product() IS 'Trigger maintaining the PillarName table';
+
+
+CREATE FUNCTION mv_pillarname_project() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        INSERT INTO PillarName (name, project, active)
+        VALUES (NEW.name, NEW.id, NEW.active);
+    ELSIF NEW.name != OLD.name or NEW.active != OLD.active THEN
+        UPDATE PillarName SET name=NEW.name, active=NEW.active
+        WHERE project=NEW.id;
+    END IF;
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_pillarname_project() IS 'Trigger maintaining the PillarName table';
+
+
+CREATE FUNCTION mv_pofiletranslator_pomsgset() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF TG_OP = 'DELETE' THEN
+        RAISE EXCEPTION
+            'Deletions from POMsgSet not supported by the POFileTranslator materialized view';
+    ELSIF TG_OP = 'UPDATE' THEN
+        IF OLD.pofile != NEW.pofile THEN
+            RAISE EXCEPTION
+                'Changing POMsgSet.pofile not supported by the POFileTranslator materialized view';
+        END IF;
+    END IF;
+    RETURN NEW;
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_pofiletranslator_pomsgset() IS 'Trigger enforing no POMsgSet deletions or POMsgSet.pofile changes';
+
+
+CREATE FUNCTION mv_pofiletranslator_posubmission() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    AS $$
+DECLARE
+    v_pofile INTEGER;
+    v_trash_old BOOLEAN;
+BEGIN
+    -- If we are deleting a row, we need to remove the existing
+    -- POFileTranslator row and reinsert the historical data if it exists.
+    -- We also treat UPDATEs that change the key (person, pofile) the same
+    -- as deletes. UPDATEs that don't change these columns are treated like
+    -- INSERTs below.
+    IF TG_OP = 'INSERT' THEN
+        v_trash_old := FALSE;
+    ELSIF TG_OP = 'DELETE' THEN
+        v_trash_old := TRUE;
+    ELSE -- UPDATE
+        v_trash_old = (
+            OLD.person != NEW.person OR OLD.pomsgset != NEW.pomsgset
+            );
+    END IF;
+
+    IF v_trash_old THEN
+
+        -- Delete the old record.
+        DELETE FROM POFileTranslator USING POMsgSet
+        WHERE POFileTranslator.pofile = POMsgSet.pofile
+            AND POFileTranslator.person = OLD.person
+            AND POMsgSet.id = OLD.pomsgset;
+
+        -- Insert a past record if there is one.
+        INSERT INTO POFileTranslator (
+            person, pofile, latest_posubmission, date_last_touched
+            )
+            SELECT DISTINCT ON (POSubmission.person, POMsgSet.pofile)
+                POSubmission.person, POMsgSet.pofile,
+                POSubmission.id, POSubmission.datecreated
+            FROM POSubmission, POMsgSet
+            WHERE POSubmission.pomsgset = POMsgSet.id
+                AND POSubmission.pomsgset = OLD.pomsgset
+                AND POSubmission.person = OLD.person
+            ORDER BY
+                POSubmission.person, POMsgSet.pofile,
+                POSubmission.datecreated DESC, POSubmission.id DESC;
+
+        -- No NEW with DELETE, so we can short circuit and leave.
+        IF TG_OP = 'DELETE' THEN
+            RETURN NULL; -- Ignored because this is an AFTER trigger
+        END IF;
+    END IF;
+
+    -- Get our new pofile id
+    SELECT INTO v_pofile POMsgSet.pofile FROM POMsgSet
+    WHERE POMsgSet.id = NEW.pomsgset;
+
+    -- Standard 'upsert' loop to avoid race conditions.
+    LOOP
+        UPDATE POFileTranslator
+            SET
+                date_last_touched = CURRENT_TIMESTAMP AT TIME ZONE 'UTC',
+                latest_posubmission = NEW.id
+            WHERE
+                person = NEW.person
+                AND pofile = v_pofile;
+        IF found THEN
+            RETURN NULL; -- Return value ignored as this is an AFTER trigger
+        END IF;
+
+        BEGIN
+            INSERT INTO POFileTranslator (person, pofile, latest_posubmission)
+            VALUES (NEW.person, v_pofile, NEW.id);
+            RETURN NULL; -- Return value ignored as this is an AFTER trigger
+        EXCEPTION WHEN unique_violation THEN
+            -- do nothing
+        END;
+    END LOOP;
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_pofiletranslator_posubmission() IS 'Trigger maintaining the POFileTranslator table';
+
+
+CREATE FUNCTION mv_pofiletranslator_translationmessage() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+DECLARE
+    v_trash_old BOOLEAN;
+BEGIN
+    -- If we are deleting a row, we need to remove the existing
+    -- POFileTranslator row and reinsert the historical data if it exists.
+    -- We also treat UPDATEs that change the key (submitter) the same
+    -- as deletes. UPDATEs that don't change these columns are treated like
+    -- INSERTs below.
+    IF TG_OP = 'INSERT' THEN
+        v_trash_old := FALSE;
+    ELSIF TG_OP = 'DELETE' THEN
+        v_trash_old := TRUE;
+    ELSE -- UPDATE
+        v_trash_old = (
+            OLD.submitter != NEW.submitter
+            );
+    END IF;
+
+    IF v_trash_old THEN
+        -- Was this somebody's most-recently-changed message?
+        -- If so, delete the entry for that change.
+        DELETE FROM POFileTranslator
+        WHERE latest_message = OLD.id;
+        IF FOUND THEN
+            -- We deleted the entry for somebody's latest contribution.
+            -- Find that person's latest remaining contribution and
+            -- create a new record for that.
+            INSERT INTO POFileTranslator (
+                person, pofile, latest_message, date_last_touched
+                )
+            SELECT DISTINCT ON (person, pofile.id)
+                new_latest_message.submitter AS person,
+                pofile.id,
+                new_latest_message.id,
+                greatest(new_latest_message.date_created,
+                         new_latest_message.date_reviewed)
+              FROM POFile
+              JOIN TranslationTemplateItem AS old_template_item
+                ON OLD.potmsgset = old_template_item.potmsgset AND
+                   old_template_item.potemplate = pofile.potemplate AND
+                   pofile.language = OLD.language
+              JOIN TranslationTemplateItem AS new_template_item
+                ON (old_template_item.potemplate =
+                     new_template_item.potemplate)
+              JOIN TranslationMessage AS new_latest_message
+                ON new_latest_message.potmsgset =
+                       new_template_item.potmsgset AND
+                   new_latest_message.language = OLD.language
+              LEFT OUTER JOIN POfileTranslator AS ExistingEntry
+                ON ExistingEntry.person = OLD.submitter AND
+                   ExistingEntry.pofile = POFile.id
+              WHERE
+                new_latest_message.submitter = OLD.submitter AND
+                ExistingEntry IS NULL
+              ORDER BY new_latest_message.submitter, pofile.id,
+                       new_latest_message.date_created DESC,
+                       new_latest_message.id DESC;
+        END IF;
+
+        -- No NEW with DELETE, so we can short circuit and leave.
+        IF TG_OP = 'DELETE' THEN
+            RETURN NULL; -- Ignored because this is an AFTER trigger
+        END IF;
+    END IF;
+
+    -- Standard 'upsert' loop to avoid race conditions.
+    LOOP
+        UPDATE POFileTranslator
+        SET
+            date_last_touched = CURRENT_TIMESTAMP AT TIME ZONE 'UTC',
+            latest_message = NEW.id
+        FROM POFile, TranslationTemplateItem
+        WHERE person = NEW.submitter AND
+              TranslationTemplateItem.potmsgset=NEW.potmsgset AND
+              TranslationTemplateItem.potemplate=pofile.potemplate AND
+              pofile.language=NEW.language AND
+              POFileTranslator.pofile = pofile.id;
+        IF found THEN
+            RETURN NULL; -- Return value ignored as this is an AFTER trigger
+        END IF;
+
+        BEGIN
+            INSERT INTO POFileTranslator (person, pofile, latest_message)
+            SELECT DISTINCT ON (NEW.submitter, pofile.id)
+                NEW.submitter, pofile.id, NEW.id
+              FROM TranslationTemplateItem
+              JOIN POFile
+                ON pofile.language = NEW.language AND
+                   pofile.potemplate = translationtemplateitem.potemplate
+              WHERE
+                TranslationTemplateItem.potmsgset = NEW.potmsgset;
+            RETURN NULL; -- Return value ignored as this is an AFTER trigger
+        EXCEPTION WHEN unique_violation THEN
+            -- do nothing
+        END;
+    END LOOP;
+END;
+$$;
+
+
+COMMENT ON FUNCTION mv_pofiletranslator_translationmessage() IS 'Trigger maintaining the POFileTranslator table';
+
+
+CREATE FUNCTION mv_validpersonorteamcache_emailaddress() RETURNS trigger
+    LANGUAGE plpythonu SECURITY DEFINER
+    AS $_$
+    # This trigger function keeps the ValidPersonOrTeamCache materialized
+    # view in sync when updates are made to the EmailAddress table.
+    # Note that if the corresponding person is a team, changes to this table
+    # have no effect.
+    PREF = 4 # Constant indicating preferred email address
+
+    if not SD.has_key("delete_plan"):
+        param_types = ["int4"]
+
+        SD["is_team"] = plpy.prepare("""
+            SELECT teamowner IS NOT NULL AS is_team FROM Person WHERE id = $1
+            """, param_types)
+
+        SD["delete_plan"] = plpy.prepare("""
+            DELETE FROM ValidPersonOrTeamCache WHERE id = $1
+            """, param_types)
+
+        SD["insert_plan"] = plpy.prepare("""
+            INSERT INTO ValidPersonOrTeamCache (id) VALUES ($1)
+            """, param_types)
+
+        SD["maybe_insert_plan"] = plpy.prepare("""
+            INSERT INTO ValidPersonOrTeamCache (id)
+            SELECT Person.id
+            FROM Person
+                JOIN EmailAddress ON Person.id = EmailAddress.person
+                LEFT OUTER JOIN ValidPersonOrTeamCache
+                    ON Person.id = ValidPersonOrTeamCache.id
+            WHERE Person.id = $1
+                AND ValidPersonOrTeamCache.id IS NULL
+                AND status = %(PREF)d
+                AND merged IS NULL
+                -- AND password IS NOT NULL
+            """ % vars(), param_types)
+
+    def is_team(person_id):
+        """Return true if person_id corresponds to a team"""
+        if person_id is None:
+            return False
+        return plpy.execute(SD["is_team"], [person_id], 1)[0]["is_team"]
+
+    class NoneDict:
+        def __getitem__(self, key):
+            return None
+
+    old = TD["old"] or NoneDict()
+    new = TD["new"] or NoneDict()
+
+    #plpy.info("old.id     == %s" % old["id"])
+    #plpy.info("old.person == %s" % old["person"])
+    #plpy.info("old.status == %s" % old["status"])
+    #plpy.info("new.id     == %s" % new["id"])
+    #plpy.info("new.person == %s" % new["person"])
+    #plpy.info("new.status == %s" % new["status"])
+
+    # Short circuit if neither person nor status has changed
+    if old["person"] == new["person"] and old["status"] == new["status"]:
+        return
+
+    # Short circuit if we are not mucking around with preferred email
+    # addresses
+    if old["status"] != PREF and new["status"] != PREF:
+        return
+
+    # Note that we have a constraint ensuring that there is only one
+    # status == PREF email address per person at any point in time.
+    # This simplifies our logic, as we know that if old.status == PREF,
+    # old.person does not have any other preferred email addresses.
+    # Also if new.status == PREF, we know new.person previously did not
+    # have a preferred email address.
+
+    if old["person"] != new["person"]:
+        if old["status"] == PREF and not is_team(old["person"]):
+            # old.person is no longer valid, unless they are a team
+            plpy.execute(SD["delete_plan"], [old["person"]])
+        if new["status"] == PREF and not is_team(new["person"]):
+            # new["person"] is now valid, or unchanged if they are a team
+            plpy.execute(SD["insert_plan"], [new["person"]])
+
+    elif old["status"] == PREF and not is_team(old["person"]):
+        # No longer valid, or unchanged if they are a team
+        plpy.execute(SD["delete_plan"], [old["person"]])
+
+    elif new["status"] == PREF and not is_team(new["person"]):
+        # May now be valid, or unchanged if they are a team.
+        plpy.execute(SD["maybe_insert_plan"], [new["person"]])
+$_$;
+
+
+COMMENT ON FUNCTION mv_validpersonorteamcache_emailaddress() IS 'A trigger for maintaining the ValidPersonOrTeamCache eager materialized view when changes are made to the EmailAddress table';
+
+
+CREATE FUNCTION mv_validpersonorteamcache_person() RETURNS trigger
+    LANGUAGE plpythonu SECURITY DEFINER
+    AS $_$
+    # This trigger function could be simplified by simply issuing
+    # one DELETE followed by one INSERT statement. However, we want to minimize
+    # expensive writes so we use this more complex logic.
+    PREF = 4 # Constant indicating preferred email address
+
+    if not SD.has_key("delete_plan"):
+        param_types = ["int4"]
+
+        SD["delete_plan"] = plpy.prepare("""
+            DELETE FROM ValidPersonOrTeamCache WHERE id = $1
+            """, param_types)
+
+        SD["maybe_insert_plan"] = plpy.prepare("""
+            INSERT INTO ValidPersonOrTeamCache (id)
+            SELECT Person.id
+            FROM Person
+                LEFT OUTER JOIN EmailAddress
+                    ON Person.id = EmailAddress.person AND status = %(PREF)d
+                LEFT OUTER JOIN ValidPersonOrTeamCache
+                    ON Person.id = ValidPersonOrTeamCache.id
+            WHERE Person.id = $1
+                AND ValidPersonOrTeamCache.id IS NULL
+                AND merged IS NULL
+                AND (teamowner IS NOT NULL OR EmailAddress.id IS NOT NULL)
+            """ % vars(), param_types)
+
+    new = TD["new"]
+    old = TD["old"]
+
+    # We should always have new, as this is not a DELETE trigger
+    assert new is not None, 'New is None'
+
+    person_id = new["id"]
+    query_params = [person_id] # All the same
+
+    # Short circuit if this is a new person (not team), as it cannot
+    # be valid until a status == 4 EmailAddress entry has been created
+    # (unless it is a team, in which case it is valid on creation)
+    if old is None:
+        if new["teamowner"] is not None:
+            plpy.execute(SD["maybe_insert_plan"], query_params)
+        return
+
+    # Short circuit if there are no relevant changes
+    if (new["teamowner"] == old["teamowner"]
+        and new["merged"] == old["merged"]):
+        return
+
+    # This function is only dealing with updates to the Person table.
+    # This means we do not have to worry about EmailAddress changes here
+
+    if (new["merged"] is not None or new["teamowner"] is None):
+        plpy.execute(SD["delete_plan"], query_params)
+    else:
+        plpy.execute(SD["maybe_insert_plan"], query_params)
+$_$;
+
+
+COMMENT ON FUNCTION mv_validpersonorteamcache_person() IS 'A trigger for maintaining the ValidPersonOrTeamCache eager materialized view when changes are made to the Person table';
+
+
+CREATE FUNCTION name_blacklist_match(text, integer) RETURNS integer
+    LANGUAGE plpythonu STABLE STRICT SECURITY DEFINER
+    SET search_path TO public
+    AS $_$
+    import re
+    name = args[0].decode("UTF-8")
+    user_id = args[1]
+
+    # Initialize shared storage, shared between invocations.
+    if not SD.has_key("regexp_select_plan"):
+
+        # All the blacklist regexps except the ones we are an admin
+        # for. These we do not check since they are not blacklisted to us.
+        SD["regexp_select_plan"] = plpy.prepare("""
+            SELECT id, regexp FROM NameBlacklist
+            WHERE admin IS NULL OR admin NOT IN (
+                SELECT team FROM TeamParticipation
+                WHERE person = $1)
+            ORDER BY id
+            """, ["integer"])
+
+        # Storage for compiled regexps
+        SD["compiled"] = {}
+
+        # admins is a celebrity and its id is immutable.
+        admins_id = plpy.execute(
+            "SELECT id FROM Person WHERE name='admins'")[0]["id"]
+
+        SD["admin_select_plan"] = plpy.prepare("""
+            SELECT TRUE FROM TeamParticipation
+            WHERE
+                TeamParticipation.team = %d
+                AND TeamParticipation.person = $1
+            LIMIT 1
+            """ % admins_id, ["integer"])
+
+        # All the blacklist regexps except those that have an admin because
+        # members of ~admin can use any name that any other admin can use.
+        SD["admin_regexp_select_plan"] = plpy.prepare("""
+            SELECT id, regexp FROM NameBlacklist
+            WHERE admin IS NULL
+            ORDER BY id
+            """, ["integer"])
+
+
+    compiled = SD["compiled"]
+
+    # Names are never blacklisted for Lauchpad admins.
+    if user_id is not None and plpy.execute(
+        SD["admin_select_plan"], [user_id]).nrows() > 0:
+        blacklist_plan = "admin_regexp_select_plan"
+    else:
+        blacklist_plan = "regexp_select_plan"
+
+    for row in plpy.execute(SD[blacklist_plan], [user_id]):
+        regexp_id = row["id"]
+        regexp_txt = row["regexp"]
+        if (compiled.get(regexp_id) is None
+            or compiled[regexp_id][0] != regexp_txt):
+            regexp = re.compile(
+                regexp_txt, re.IGNORECASE | re.UNICODE | re.VERBOSE
+                )
+            compiled[regexp_id] = (regexp_txt, regexp)
+        else:
+            regexp = compiled[regexp_id][1]
+        if regexp.search(name) is not None:
+            return regexp_id
+    return None
+$_$;
+
+
+COMMENT ON FUNCTION name_blacklist_match(text, integer) IS 'Return the id of the row in the NameBlacklist table that matches the given name, or NULL if no regexps in the NameBlacklist table match.';
+
+
+CREATE FUNCTION null_count(p_values anyarray) RETURNS integer
+    LANGUAGE plpgsql IMMUTABLE STRICT
+    AS $$
+DECLARE
+    v_index integer;
+    v_null_count integer := 0;
+BEGIN
+    FOR v_index IN array_lower(p_values,1)..array_upper(p_values,1) LOOP
+        IF p_values[v_index] IS NULL THEN
+            v_null_count := v_null_count + 1;
+        END IF;
+    END LOOP;
+    RETURN v_null_count;
+END;
+$$;
+
+
+COMMENT ON FUNCTION null_count(p_values anyarray) IS 'Return the number of NULLs in the first row of the given array.';
+
+
+CREATE FUNCTION packageset_deleted_trig() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    DELETE FROM flatpackagesetinclusion
+      WHERE parent = OLD.id AND child = OLD.id;
+
+    -- A package set was deleted; it may have participated in package set
+    -- inclusion relations in a sub/superset role; delete all inclusion
+    -- relationships in which it participated.
+    DELETE FROM packagesetinclusion
+      WHERE parent = OLD.id OR child = OLD.id;
+    RETURN OLD;
+END;
+$$;
+
+
+COMMENT ON FUNCTION packageset_deleted_trig() IS 'Remove any DAG edges leading to/from the deleted package set.';
+
+
+CREATE FUNCTION packageset_inserted_trig() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    -- A new package set was inserted; make it a descendent of itself in
+    -- the flattened package set inclusion table in order to facilitate
+    -- querying.
+    INSERT INTO flatpackagesetinclusion(parent, child)
+      VALUES (NEW.id, NEW.id);
+    RETURN NULL;
+END;
+$$;
+
+
+COMMENT ON FUNCTION packageset_inserted_trig() IS 'Insert self-referencing DAG edge when a new package set is inserted.';
+
+
+CREATE FUNCTION packagesetinclusion_deleted_trig() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    -- A package set inclusion relationship was deleted i.e. a set M
+    -- ceases to include another set N as a subset.
+    -- For an explanation of the queries below please see page 5 of
+    -- "Maintaining Transitive Closure of Graphs in SQL"
+    -- http://www.comp.nus.edu.sg/~wongls/psZ/dlsw-ijit97-16.ps
+    CREATE TEMP TABLE tmp_fpsi_suspect(
+        parent integer NOT NULL,
+        child integer NOT NULL);
+    CREATE TEMP TABLE tmp_fpsi_trusted(
+        parent integer NOT NULL,
+        child integer NOT NULL);
+    CREATE TEMP TABLE tmp_fpsi_good(
+        parent integer NOT NULL,
+        child integer NOT NULL);
+
+    INSERT INTO tmp_fpsi_suspect (
+        SELECT X.parent, Y.child
+        FROM flatpackagesetinclusion X, flatpackagesetinclusion Y
+        WHERE X.child = OLD.parent AND Y.parent = OLD.child
+      UNION
+        SELECT X.parent, OLD.child FROM flatpackagesetinclusion X
+        WHERE X.child = OLD.parent
+      UNION
+        SELECT OLD.parent, X.child FROM flatpackagesetinclusion X
+        WHERE X.parent = OLD.child
+      UNION
+        SELECT OLD.parent, OLD.child
+        );
+
+    INSERT INTO tmp_fpsi_trusted (
+        SELECT parent, child FROM flatpackagesetinclusion
+        EXCEPT
+        SELECT parent, child FROM tmp_fpsi_suspect
+      UNION
+        SELECT parent, child FROM packagesetinclusion psi
+        WHERE psi.parent != OLD.parent AND psi.child != OLD.child
+        );
+
+    INSERT INTO tmp_fpsi_good (
+        SELECT parent, child FROM tmp_fpsi_trusted
+      UNION
+        SELECT T1.parent, T2.child
+        FROM tmp_fpsi_trusted T1, tmp_fpsi_trusted T2
+        WHERE T1.child = T2.parent
+      UNION
+        SELECT T1.parent, T3.child
+        FROM tmp_fpsi_trusted T1, tmp_fpsi_trusted T2, tmp_fpsi_trusted T3
+        WHERE T1.child = T2.parent AND T2.child = T3.parent
+        );
+
+    DELETE FROM flatpackagesetinclusion fpsi
+    WHERE NOT EXISTS (
+        SELECT * FROM tmp_fpsi_good T
+        WHERE T.parent = fpsi.parent AND T.child = fpsi.child);
+
+    DROP TABLE tmp_fpsi_good;
+    DROP TABLE tmp_fpsi_trusted;
+    DROP TABLE tmp_fpsi_suspect;
+
+    RETURN OLD;
+END;
+$$;
+
+
+COMMENT ON FUNCTION packagesetinclusion_deleted_trig() IS 'Maintain the transitive closure in the DAG when an edge leading to/from a package set is deleted.';
+
+
+CREATE FUNCTION packagesetinclusion_inserted_trig() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    DECLARE
+        parent_name text;
+        child_name text;
+        parent_distroseries text;
+        child_distroseries text;
+    BEGIN
+        -- Make sure that the package sets being associated here belong
+        -- to the same distro series.
+        IF (SELECT parent.distroseries != child.distroseries
+            FROM packageset parent, packageset child
+            WHERE parent.id = NEW.parent AND child.id = NEW.child)
+        THEN
+            SELECT name INTO parent_name FROM packageset WHERE id = NEW.parent;
+            SELECT name INTO child_name FROM packageset WHERE id = NEW.child;
+            SELECT ds.name INTO parent_distroseries FROM packageset ps, distroseries ds WHERE ps.id = NEW.parent AND ps.distroseries = ds.id;
+            SELECT ds.name INTO child_distroseries FROM packageset ps, distroseries ds WHERE ps.id = NEW.child AND ps.distroseries = ds.id;
+            RAISE EXCEPTION 'Package sets % and % belong to different distro series (to % and % respectively) and thus cannot be associated.', child_name, parent_name, child_distroseries, parent_distroseries;
+        END IF;
+
+        IF EXISTS(
+            SELECT * FROM flatpackagesetinclusion
+            WHERE parent = NEW.child AND child = NEW.parent LIMIT 1)
+        THEN
+            SELECT name INTO parent_name FROM packageset WHERE id = NEW.parent;
+            SELECT name INTO child_name FROM packageset WHERE id = NEW.child;
+            RAISE EXCEPTION 'Package set % already includes %. Adding (% -> %) would introduce a cycle in the package set graph (DAG).', child_name, parent_name, parent_name, child_name;
+        END IF;
+    END;
+    -- A new package set inclusion relationship was inserted i.e. a set M
+    -- now includes another set N as a subset.
+    -- For an explanation of the queries below please see page 4 of
+    -- "Maintaining Transitive Closure of Graphs in SQL"
+    -- http://www.comp.nus.edu.sg/~wongls/psZ/dlsw-ijit97-16.ps
+    CREATE TEMP TABLE tmp_fpsi_new(
+        parent integer NOT NULL,
+        child integer NOT NULL);
+
+    INSERT INTO tmp_fpsi_new (
+        SELECT
+            X.parent AS parent, NEW.child AS child
+        FROM flatpackagesetinclusion X WHERE X.child = NEW.parent
+      UNION
+        SELECT
+            NEW.parent AS parent, X.child AS child
+        FROM flatpackagesetinclusion X WHERE X.parent = NEW.child
+      UNION
+        SELECT
+            X.parent AS parent, Y.child AS child
+        FROM flatpackagesetinclusion X, flatpackagesetinclusion Y
+        WHERE X.child = NEW.parent AND Y.parent = NEW.child
+        );
+    INSERT INTO tmp_fpsi_new(parent, child) VALUES(NEW.parent, NEW.child);
+
+    INSERT INTO flatpackagesetinclusion(parent, child) (
+        SELECT
+            parent, child FROM tmp_fpsi_new
+        EXCEPT
+        SELECT F.parent, F.child FROM flatpackagesetinclusion F
+        );
+
+    DROP TABLE tmp_fpsi_new;
+
+    RETURN NULL;
+END;
+$$;
+
+
+COMMENT ON FUNCTION packagesetinclusion_inserted_trig() IS 'Maintain the transitive closure in the DAG for a newly inserted edge leading to/from a package set.';
+
+
+CREATE FUNCTION person_sort_key(displayname text, name text) RETURNS text
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    # NB: If this implementation is changed, the person_sort_idx needs to be
+    # rebuilt along with any other indexes using it.
+    import re
+
+    try:
+        strip_re = SD["strip_re"]
+    except KeyError:
+        strip_re = re.compile("(?:[^\w\s]|[\d_])", re.U)
+        SD["strip_re"] = strip_re
+
+    displayname, name = args
+
+    # Strip noise out of displayname. We do not have to bother with
+    # name, as we know it is just plain ascii.
+    displayname = strip_re.sub('', displayname.decode('UTF-8').lower())
+    return ("%s, %s" % (displayname.strip(), name)).encode('UTF-8')
+$$;
+
+
+COMMENT ON FUNCTION person_sort_key(displayname text, name text) IS 'Return a string suitable for sorting people on, generated by stripping noise out of displayname and concatenating name';
+
 
 CREATE FUNCTION pgstattuple(text) RETURNS pgstattuple_type
-    AS '$libdir/pgstattuple', 'pgstattuple'
-    LANGUAGE c STRICT;
+    LANGUAGE c STRICT
+    AS '$libdir/pgstattuple', 'pgstattuple';
+
 
 CREATE FUNCTION pgstattuple(oid) RETURNS pgstattuple_type
-    AS '$libdir/pgstattuple', 'pgstattuplebyid'
-    LANGUAGE c STRICT;
+    LANGUAGE c STRICT
+    AS '$libdir/pgstattuple', 'pgstattuplebyid';
+
 
 CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler
-    AS '$libdir/plpgsql', 'plpgsql_call_handler'
-    LANGUAGE c;
+    LANGUAGE c
+    AS '$libdir/plpgsql', 'plpgsql_call_handler';
+
 
 CREATE FUNCTION plpython_call_handler() RETURNS language_handler
-    AS '$libdir/plpython', 'plpython_call_handler'
-    LANGUAGE c;
+    LANGUAGE c
+    AS '$libdir/plpython', 'plpython_call_handler';
+
+
+CREATE FUNCTION questionmessage_copy_owner_from_message() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        IF NEW.owner is NULL THEN
+            UPDATE QuestionMessage
+            SET owner = Message.owner FROM
+            Message WHERE
+            Message.id = NEW.message AND
+            QuestionMessage.id = NEW.id;
+        END IF;
+    ELSIF NEW.message != OLD.message THEN
+        UPDATE QuestionMessage
+        SET owner = Message.owner FROM
+        Message WHERE
+        Message.id = NEW.message AND
+        QuestionMessage.id = NEW.id;
+    END IF;
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION questionmessage_copy_owner_from_message() IS 'Copies the message owner into QuestionMessage when QuestionMessage changes.';
+
+
+CREATE FUNCTION replication_lag() RETURNS interval
+    LANGUAGE plpgsql STABLE SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+    DECLARE
+        v_lag interval;
+    BEGIN
+        SELECT INTO v_lag max(st_lag_time) FROM _sl.sl_status;
+        RETURN v_lag;
+    -- Slony-I not installed here - non-replicated setup.
+    EXCEPTION
+        WHEN invalid_schema_name THEN
+            RETURN NULL;
+        WHEN undefined_table THEN
+            RETURN NULL;
+    END;
+$$;
+
+
+COMMENT ON FUNCTION replication_lag() IS 'Returns the worst lag time in our cluster, or NULL if not a replicated installation. Only returns meaningful results on the lpmain replication set master.';
+
+
+CREATE FUNCTION replication_lag(node_id integer) RETURNS interval
+    LANGUAGE plpgsql STABLE SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+    DECLARE
+        v_lag interval;
+    BEGIN
+        SELECT INTO v_lag st_lag_time FROM _sl.sl_status
+            WHERE st_origin = _sl.getlocalnodeid('_sl')
+                AND st_received = node_id;
+        RETURN v_lag;
+    -- Slony-I not installed here - non-replicated setup.
+    EXCEPTION
+        WHEN invalid_schema_name THEN
+            RETURN NULL;
+        WHEN undefined_table THEN
+            RETURN NULL;
+    END;
+$$;
+
+
+COMMENT ON FUNCTION replication_lag(node_id integer) IS 'Returns the lag time of the lpmain replication set to the given node, or NULL if not a replicated installation. The node id parameter can be obtained by calling getlocalnodeid() on the relevant database. This function only returns meaningful results on the lpmain replication set master.';
+
+
+CREATE FUNCTION sane_version(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $_$
+    import re
+    if re.search("""^(?ix)
+        [0-9a-z]
+        ( [0-9a-z] | [0-9a-z.-]*[0-9a-z] )*
+        $""", args[0]):
+        return 1
+    return 0
+$_$;
+
+
+COMMENT ON FUNCTION sane_version(text) IS 'A sane version number for use by ProductRelease and DistroRelease. We may make it less strict if required, but it would be nice if we can enforce simple version strings because we use them in URLs';
+
+
+CREATE FUNCTION set_bug_date_last_message() RETURNS trigger
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        UPDATE Bug
+        SET date_last_message = CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
+        WHERE Bug.id = NEW.bug;
+    ELSE
+        UPDATE Bug
+        SET date_last_message = max_datecreated
+        FROM (
+            SELECT BugMessage.bug, max(Message.datecreated) AS max_datecreated
+            FROM BugMessage, Message
+            WHERE BugMessage.id <> OLD.id
+                AND BugMessage.bug = OLD.bug
+                AND BugMessage.message = Message.id
+            GROUP BY BugMessage.bug
+            ) AS MessageSummary
+        WHERE Bug.id = MessageSummary.bug;
+    END IF;
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION set_bug_date_last_message() IS 'AFTER INSERT trigger on BugMessage maintaining the Bug.date_last_message column';
+
+
+CREATE FUNCTION set_bug_message_count() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF TG_OP = 'UPDATE' THEN
+        IF NEW.bug = OLD.bug THEN
+            RETURN NULL; -- Ignored - this is an AFTER trigger.
+        END IF;
+    END IF;
+
+    IF TG_OP <> 'DELETE' THEN
+        UPDATE Bug SET message_count = message_count + 1
+        WHERE Bug.id = NEW.bug;
+    END IF;
+
+    IF TG_OP <> 'INSERT' THEN
+        UPDATE Bug SET message_count = message_count - 1
+        WHERE Bug.id = OLD.bug;
+    END IF;
+
+    RETURN NULL; -- Ignored - this is an AFTER trigger.
+END;
+$$;
+
+
+COMMENT ON FUNCTION set_bug_message_count() IS 'AFTER UPDATE trigger on BugAffectsPerson maintaining the Bug.users_affected_count column';
+
+
+CREATE FUNCTION set_bug_number_of_duplicates() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    -- Short circuit on an update that doesn't change duplicateof
+    IF TG_OP = 'UPDATE' THEN
+        IF NEW.duplicateof = OLD.duplicateof THEN
+            RETURN NULL; -- Ignored - this is an AFTER trigger
+        END IF;
+    END IF;
+
+    -- For update or delete, possibly decrement a bug's dupe count
+    IF TG_OP <> 'INSERT' THEN
+        IF OLD.duplicateof IS NOT NULL THEN
+            UPDATE Bug SET number_of_duplicates = number_of_duplicates - 1
+                WHERE Bug.id = OLD.duplicateof;
+        END IF;
+    END IF;
+
+    -- For update or insert, possibly increment a bug's dupe cout
+    IF TG_OP <> 'DELETE' THEN
+        IF NEW.duplicateof IS NOT NULL THEN
+            UPDATE Bug SET number_of_duplicates = number_of_duplicates + 1
+                WHERE Bug.id = NEW.duplicateof;
+        END IF;
+    END IF;
+
+    RETURN NULL; -- Ignored - this is an AFTER trigger
+END;
+$$;
+
+
+COMMENT ON FUNCTION set_bug_number_of_duplicates() IS 'AFTER UPDATE trigger on Bug maintaining the Bug.number_of_duplicates column';
+
+
+CREATE FUNCTION set_bug_users_affected_count() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        IF NEW.affected = TRUE THEN
+            UPDATE Bug
+            SET users_affected_count = users_affected_count + 1
+            WHERE Bug.id = NEW.bug;
+        ELSE
+            UPDATE Bug
+            SET users_unaffected_count = users_unaffected_count + 1
+            WHERE Bug.id = NEW.bug;
+        END IF;
+    END IF;
+
+    IF TG_OP = 'DELETE' THEN
+        IF OLD.affected = TRUE THEN
+            UPDATE Bug
+            SET users_affected_count = users_affected_count - 1
+            WHERE Bug.id = OLD.bug;
+        ELSE
+            UPDATE Bug
+            SET users_unaffected_count = users_unaffected_count - 1
+            WHERE Bug.id = OLD.bug;
+        END IF;
+    END IF;
+
+    IF TG_OP = 'UPDATE' THEN
+        IF OLD.affected <> NEW.affected THEN
+            IF NEW.affected THEN
+                UPDATE Bug
+                SET users_affected_count = users_affected_count + 1,
+                    users_unaffected_count = users_unaffected_count - 1
+                WHERE Bug.id = OLD.bug;
+            ELSE
+                UPDATE Bug
+                SET users_affected_count = users_affected_count - 1,
+                    users_unaffected_count = users_unaffected_count + 1
+                WHERE Bug.id = OLD.bug;
+            END IF;
+        END IF;
+    END IF;
+
+    RETURN NULL;
+END;
+$$;
+
+
+CREATE FUNCTION set_bugtask_date_milestone_set() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        -- If the inserted row as a milestone set, set date_milestone_set.
+        IF NEW.milestone IS NOT NULL THEN
+            UPDATE BugTask
+            SET date_milestone_set = CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
+            WHERE BugTask.id = NEW.id;
+        END IF;
+    END IF;
+
+    IF TG_OP = 'UPDATE' THEN
+        IF OLD.milestone IS NULL THEN
+            -- If there was no milestone set, check if the new row has a
+            -- milestone set and set date_milestone_set.
+            IF NEW.milestone IS NOT NULL THEN
+                UPDATE BugTask
+                SET date_milestone_set = CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
+                WHERE BugTask.id = NEW.id;
+            END IF;
+        ELSE
+            IF NEW.milestone IS NULL THEN
+                -- If the milestone was unset, clear date_milestone_set.
+                UPDATE BugTask
+                SET date_milestone_set = NULL
+                WHERE BugTask.id = NEW.id;
+            ELSE
+                -- Update date_milestone_set if the bug task was
+                -- targeted to another milestone.
+                IF NEW.milestone != OLD.milestone THEN
+                    UPDATE BugTask
+                    SET date_milestone_set = CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
+                    WHERE BugTask.id = NEW.id;
+                END IF;
+
+            END IF;
+        END IF;
+    END IF;
+
+    RETURN NULL; -- Ignored - this is an AFTER trigger.
+END;
+$$;
+
+
+COMMENT ON FUNCTION set_bugtask_date_milestone_set() IS 'Update BugTask.date_milestone_set when BugTask.milestone is changed.';
+
+
+CREATE FUNCTION set_date_status_set() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+    IF OLD.status <> NEW.status THEN
+        NEW.date_status_set = CURRENT_TIMESTAMP AT TIME ZONE 'UTC';
+    END IF;
+    RETURN NEW;
+END;
+$$;
+
+
+COMMENT ON FUNCTION set_date_status_set() IS 'BEFORE UPDATE trigger on Account that maintains the Account.date_status_set column.';
+
+
+CREATE FUNCTION set_openid_identifier() RETURNS trigger
+    LANGUAGE plpythonu
+    AS $$
+    # If someone is trying to explicitly set the openid_identifier, let them.
+    # This also causes openid_identifiers to be left alone if this is an
+    # UPDATE trigger.
+    if TD['new']['openid_identifier'] is not None:
+        return None
+
+    from random import choice
+
+    # Non display confusing characters
+    chars = '34678bcdefhkmnprstwxyzABCDEFGHJKLMNPQRTWXY'
+
+    # character length of tokens. Can be increased, decreased or even made
+    # random - Launchpad does not care. 7 means it takes 40 bytes to store
+    # a null-terminated Launchpad identity URL on the current domain name.
+    length=7
+
+    loop_count = 0
+    while loop_count < 20000:
+        # Generate a random openid_identifier
+        oid = ''.join(choice(chars) for count in range(length))
+
+        # Check if the oid is already in the db, although this is pretty
+        # unlikely
+        rv = plpy.execute("""
+            SELECT COUNT(*) AS num FROM Person WHERE openid_identifier = '%s'
+            """ % oid, 1)
+        if rv[0]['num'] == 0:
+            TD['new']['openid_identifier'] = oid
+            return "MODIFY"
+        loop_count += 1
+        if loop_count == 1:
+            plpy.warning(
+                'Clash generating unique openid_identifier. '
+                'Increase length if you see this warning too much.')
+    plpy.error(
+        "Unable to generate unique openid_identifier. "
+        "Need to increase length of tokens.")
+$$;
+
+
+CREATE FUNCTION set_shipit_normalized_address() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+    BEGIN
+        NEW.normalized_address =
+            lower(
+                -- Strip off everything that's not alphanumeric
+                -- characters.
+                regexp_replace(
+                    coalesce(NEW.addressline1, '') || ' ' ||
+                    coalesce(NEW.addressline2, '') || ' ' ||
+                    coalesce(NEW.city, ''),
+                    '[^a-zA-Z0-9]+', '', 'g'));
+        RETURN NEW;
+    END;
+$$;
+
+
+COMMENT ON FUNCTION set_shipit_normalized_address() IS 'Store a normalized concatenation of the request''s address into the normalized_address column.';
+
+
+CREATE FUNCTION sha1(text) RETURNS character
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    import hashlib
+    return hashlib.sha1(args[0]).hexdigest()
+$$;
+
+
+COMMENT ON FUNCTION sha1(text) IS 'Return the SHA1 one way cryptographic hash as a string of 40 hex digits';
+
+
+CREATE FUNCTION summarise_bug(bug_row bug) RETURNS void
+    LANGUAGE plpgsql
+    AS $$
+DECLARE
+    d bugsummary%ROWTYPE;
+BEGIN
+    PERFORM ensure_bugsummary_temp_journal();
+    FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
+        d.count = 1;
+        PERFORM bug_summary_temp_journal_ins(d);
+    END LOOP;
+END;
+$$;
+
+
+COMMENT ON FUNCTION summarise_bug(bug_row bug) IS 'AFTER summarise a bug row into bugsummary.';
+
+
+CREATE FUNCTION ulower(text) RETURNS text
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    return args[0].decode('utf8').lower().encode('utf8')
+$$;
+
+
+COMMENT ON FUNCTION ulower(text) IS 'Return the lower case version of a UTF-8 encoded string.';
+
+
+CREATE FUNCTION unsummarise_bug(bug_row bug) RETURNS void
+    LANGUAGE plpgsql
+    AS $$
+DECLARE
+    d bugsummary%ROWTYPE;
+BEGIN
+    PERFORM ensure_bugsummary_temp_journal();
+    FOR d IN SELECT * FROM bugsummary_locations(BUG_ROW) LOOP
+        d.count = -1;
+        PERFORM bug_summary_temp_journal_ins(d);
+    END LOOP;
+END;
+$$;
+
+
+COMMENT ON FUNCTION unsummarise_bug(bug_row bug) IS 'AFTER unsummarise a bug row from bugsummary.';
+
+
+CREATE FUNCTION update_branch_name_cache() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+DECLARE
+    needs_update boolean := FALSE;
+BEGIN
+    IF TG_OP = 'INSERT' THEN
+        needs_update := TRUE;
+    ELSIF (NEW.owner_name IS NULL
+        OR NEW.unique_name IS NULL
+        OR OLD.owner_name <> NEW.owner_name
+        OR OLD.unique_name <> NEW.unique_name
+        OR (NEW.target_suffix IS NULL <> OLD.target_suffix IS NULL)
+        OR COALESCE(OLD.target_suffix, '') <> COALESCE(NEW.target_suffix, '')
+        OR OLD.name <> NEW.name
+        OR OLD.owner <> NEW.owner
+        OR COALESCE(OLD.product, -1) <> COALESCE(NEW.product, -1)
+        OR COALESCE(OLD.distroseries, -1) <> COALESCE(NEW.distroseries, -1)
+        OR COALESCE(OLD.sourcepackagename, -1)
+            <> COALESCE(NEW.sourcepackagename, -1)) THEN
+        needs_update := TRUE;
+    END IF;
+
+    IF needs_update THEN
+        SELECT
+            Person.name AS owner_name,
+            COALESCE(Product.name, SPN.name) AS target_suffix,
+            '~' || Person.name || '/' || COALESCE(
+                Product.name,
+                Distribution.name || '/' || Distroseries.name
+                    || '/' || SPN.name,
+                '+junk') || '/' || NEW.name AS unique_name
+        INTO NEW.owner_name, NEW.target_suffix, NEW.unique_name
+        FROM Person
+        LEFT OUTER JOIN DistroSeries ON NEW.distroseries = DistroSeries.id
+        LEFT OUTER JOIN Product ON NEW.product = Product.id
+        LEFT OUTER JOIN Distribution
+            ON Distroseries.distribution = Distribution.id
+        LEFT OUTER JOIN SourcepackageName AS SPN
+            ON SPN.id = NEW.sourcepackagename
+        WHERE Person.id = NEW.owner;
+    END IF;
+
+    RETURN NEW;
+END;
+$$;
+
+
+COMMENT ON FUNCTION update_branch_name_cache() IS 'Maintain the cached name columns in Branch.';
+
+
+CREATE FUNCTION update_database_disk_utilization() RETURNS void
+    LANGUAGE sql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+    INSERT INTO DatabaseDiskUtilization
+    SELECT
+        CURRENT_TIMESTAMP AT TIME ZONE 'UTC',
+        namespace, name,
+        sub_namespace, sub_name,
+        kind,
+        (namespace || '.' ||  name || COALESCE(
+                '/' || sub_namespace || '.' || sub_name, '')) AS sort,
+        (stat).table_len,
+        (stat).tuple_count,
+        (stat).tuple_len,
+        (stat).tuple_percent,
+        (stat).dead_tuple_count,
+        (stat).dead_tuple_len,
+        (stat).dead_tuple_percent,
+        (stat).free_space,
+        (stat).free_percent
+    FROM (
+        SELECT
+            pg_namespace.nspname AS namespace,
+            pg_class.relname AS name,
+            NULL AS sub_namespace,
+            NULL AS sub_name,
+            pg_class.relkind AS kind,
+            pgstattuple(pg_class.oid) AS stat
+        FROM pg_class, pg_namespace
+        WHERE
+            pg_class.relnamespace = pg_namespace.oid
+            AND pg_class.relkind = 'r'
+            AND pg_table_is_visible(pg_class.oid)
+
+        UNION ALL
+        
+        SELECT
+            pg_namespace_table.nspname AS namespace,
+            pg_class_table.relname AS name,
+            pg_namespace_index.nspname AS sub_namespace,
+            pg_class_index.relname AS sub_name,
+            pg_class_index.relkind AS kind,
+            pgstattuple(pg_class_index.oid) AS stat
+        FROM
+            pg_namespace AS pg_namespace_table,
+            pg_namespace AS pg_namespace_index,
+            pg_class AS pg_class_table,
+            pg_class AS pg_class_index,
+            pg_index
+        WHERE
+            pg_class_index.relkind = 'i'
+            AND pg_table_is_visible(pg_class_table.oid)
+            AND pg_class_index.relnamespace = pg_namespace_index.oid
+            AND pg_class_table.relnamespace = pg_namespace_table.oid
+            AND pg_index.indexrelid = pg_class_index.oid
+            AND pg_index.indrelid = pg_class_table.oid
+
+        UNION ALL
+
+        -- TOAST tables
+        SELECT
+            pg_namespace_table.nspname AS namespace,
+            pg_class_table.relname AS name,
+            pg_namespace_toast.nspname AS sub_namespace,
+            pg_class_toast.relname AS sub_name,
+            pg_class_toast.relkind AS kind,
+            pgstattuple(pg_class_toast.oid) AS stat
+        FROM
+            pg_namespace AS pg_namespace_table,
+            pg_namespace AS pg_namespace_toast,
+            pg_class AS pg_class_table,
+            pg_class AS pg_class_toast
+        WHERE
+            pg_class_toast.relnamespace = pg_namespace_toast.oid
+            AND pg_table_is_visible(pg_class_table.oid)
+            AND pg_class_table.relnamespace = pg_namespace_table.oid
+            AND pg_class_toast.oid = pg_class_table.reltoastrelid
+
+        UNION ALL
+
+        -- TOAST indexes
+        SELECT
+            pg_namespace_table.nspname AS namespace,
+            pg_class_table.relname AS name,
+            pg_namespace_index.nspname AS sub_namespace,
+            pg_class_index.relname AS sub_name,
+            pg_class_index.relkind AS kind,
+            pgstattuple(pg_class_index.oid) AS stat
+        FROM
+            pg_namespace AS pg_namespace_table,
+            pg_namespace AS pg_namespace_index,
+            pg_class AS pg_class_table,
+            pg_class AS pg_class_index,
+            pg_class AS pg_class_toast
+        WHERE
+            pg_class_table.relnamespace = pg_namespace_table.oid
+            AND pg_table_is_visible(pg_class_table.oid)
+            AND pg_class_index.relnamespace = pg_namespace_index.oid
+            AND pg_class_table.reltoastrelid = pg_class_toast.oid
+            AND pg_class_index.oid = pg_class_toast.reltoastidxid
+        ) AS whatever;
+$$;
+
+
+CREATE FUNCTION update_database_stats() RETURNS void
+    LANGUAGE plpythonu SECURITY DEFINER
+    SET search_path TO public
+    AS $_$
+    import re
+    import subprocess
+
+    # Prune DatabaseTableStats and insert current data.
+    # First, detect if the statistics have been reset.
+    stats_reset = plpy.execute("""
+        SELECT *
+        FROM
+            pg_catalog.pg_stat_user_tables AS NowStat,
+            DatabaseTableStats AS LastStat
+        WHERE
+            LastStat.date_created = (
+                SELECT max(date_created) FROM DatabaseTableStats)
+            AND NowStat.schemaname = LastStat.schemaname
+            AND NowStat.relname = LastStat.relname
+            AND (
+                NowStat.seq_scan < LastStat.seq_scan
+                OR NowStat.idx_scan < LastStat.idx_scan
+                OR NowStat.n_tup_ins < LastStat.n_tup_ins
+                OR NowStat.n_tup_upd < LastStat.n_tup_upd
+                OR NowStat.n_tup_del < LastStat.n_tup_del
+                OR NowStat.n_tup_hot_upd < LastStat.n_tup_hot_upd)
+        LIMIT 1
+        """, 1).nrows() > 0
+    if stats_reset:
+        # The database stats have been reset. We cannot calculate
+        # deltas because we do not know when this happened. So we trash
+        # our records as they are now useless to us. We could be more
+        # sophisticated about this, but this should only happen
+        # when an admin explicitly resets the statistics or if the
+        # database is rebuilt.
+        plpy.notice("Stats wraparound. Purging DatabaseTableStats")
+        plpy.execute("DELETE FROM DatabaseTableStats")
+    else:
+        plpy.execute("""
+            DELETE FROM DatabaseTableStats
+            WHERE date_created < (CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
+                - CAST('21 days' AS interval));
+            """)
+    # Insert current data.
+    plpy.execute("""
+        INSERT INTO DatabaseTableStats
+            SELECT
+                CURRENT_TIMESTAMP AT TIME ZONE 'UTC',
+                schemaname, relname, seq_scan, seq_tup_read,
+                coalesce(idx_scan, 0), coalesce(idx_tup_fetch, 0),
+                n_tup_ins, n_tup_upd, n_tup_del,
+                n_tup_hot_upd, n_live_tup, n_dead_tup, last_vacuum,
+                last_autovacuum, last_analyze, last_autoanalyze
+            FROM pg_catalog.pg_stat_user_tables;
+        """)
+
+    # Prune DatabaseCpuStats. Calculate CPU utilization information
+    # and insert current data.
+    plpy.execute("""
+        DELETE FROM DatabaseCpuStats
+        WHERE date_created < (CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
+            - CAST('21 days' AS interval));
+        """)
+    dbname = plpy.execute(
+        "SELECT current_database() AS dbname", 1)[0]['dbname']
+    ps = subprocess.Popen(
+        ["ps", "-C", "postgres", "--no-headers", "-o", "cp,args"],
+        stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT)
+    stdout, stderr = ps.communicate()
+    cpus = {}
+    # We make the username match non-greedy so the trailing \d eats
+    # trailing digits from the database username. This collapses
+    # lpnet1, lpnet2 etc. into just lpnet.
+    ps_re = re.compile(
+        r"(?m)^\s*(\d+)\spostgres:\s(\w+?)\d*\s%s\s" % dbname)
+    for ps_match in ps_re.finditer(stdout):
+        cpu, username = ps_match.groups()
+        cpus[username] = int(cpu) + cpus.setdefault(username, 0)
+    cpu_ins = plpy.prepare(
+        "INSERT INTO DatabaseCpuStats (username, cpu) VALUES ($1, $2)",
+        ["text", "integer"])
+    for cpu_tuple in cpus.items():
+        plpy.execute(cpu_ins, cpu_tuple)
+$_$;
+
+
+COMMENT ON FUNCTION update_database_stats() IS 'Copies rows from pg_stat_user_tables into DatabaseTableStats. We use a stored procedure because it is problematic for us to grant permissions on objects in the pg_catalog schema.';
+
+
+CREATE FUNCTION update_replication_lag_cache() RETURNS boolean
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+    BEGIN
+        DELETE FROM DatabaseReplicationLag;
+        INSERT INTO DatabaseReplicationLag (node, lag)
+            SELECT st_received, st_lag_time FROM _sl.sl_status
+            WHERE st_origin = _sl.getlocalnodeid('_sl');
+        RETURN TRUE;
+    -- Slony-I not installed here - non-replicated setup.
+    EXCEPTION
+        WHEN invalid_schema_name THEN
+            RETURN FALSE;
+        WHEN undefined_table THEN
+            RETURN FALSE;
+    END;
+$$;
+
+
+COMMENT ON FUNCTION update_replication_lag_cache() IS 'Updates the DatabaseReplicationLag materialized view.';
+
+
+CREATE FUNCTION update_transitively_private(start_branch integer, _root_branch integer DEFAULT NULL::integer, _root_transitively_private boolean DEFAULT NULL::boolean) RETURNS void
+    LANGUAGE plpgsql SECURITY DEFINER
+    SET search_path TO public
+    AS $$
+DECLARE
+    root_transitively_private boolean := _root_transitively_private;
+    root_branch int := _root_branch;
+BEGIN
+    IF root_transitively_private IS NULL THEN
+        -- We can't just trust the transitively_private flag of the
+        -- branch we are stacked on, as if we are updating multiple
+        -- records they will be updated in an indeterminate order.
+        -- We need a recursive query.
+        UPDATE Branch SET transitively_private = (
+            WITH RECURSIVE stacked_branches AS (
+                SELECT
+                    top_branch.id, top_branch.stacked_on, top_branch.private
+                FROM Branch AS top_branch
+                WHERE top_branch.id = start_branch
+                UNION ALL
+                SELECT
+                    sub_branch.id, sub_branch.stacked_on, sub_branch.private
+                FROM stacked_branches, Branch AS sub_branch
+                WHERE
+                    stacked_branches.stacked_on = sub_branch.id
+                    AND stacked_branches.stacked_on != start_branch
+                    -- Shortcircuit. No need to recurse if already private.
+                    AND stacked_branches.private IS FALSE
+                    )
+            SELECT COUNT(*) > 0
+            FROM stacked_branches
+            WHERE private IS TRUE)
+        WHERE Branch.id = start_branch
+        RETURNING transitively_private INTO root_transitively_private;
+        root_branch := start_branch;
+    ELSE
+        -- Now we have calculated the correct transitively_private flag
+        -- we can trust it.
+        UPDATE Branch SET
+            transitively_private = GREATEST(private, root_transitively_private)
+        WHERE id = root_branch;
+    END IF;
+
+    -- Recurse to branches stacked on this one.
+    PERFORM update_transitively_private(
+        start_branch, id, GREATEST(private, root_transitively_private))
+    FROM Branch WHERE stacked_on = root_branch AND id != start_branch;
+END;
+$$;
+
+
+COMMENT ON FUNCTION update_transitively_private(start_branch integer, _root_branch integer, _root_transitively_private boolean) IS 'A branch is transitively private if it is private or is stacked on any transitively private branches.';
+
+
+CREATE FUNCTION valid_absolute_url(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    from urlparse import urlparse, uses_netloc
+    # Extend list of schemes that specify netloc. We can drop sftp
+    # with Python 2.5 in the DB.
+    if 'git' not in uses_netloc:
+        uses_netloc.insert(0, 'sftp')
+        uses_netloc.insert(0, 'bzr')
+        uses_netloc.insert(0, 'bzr+ssh')
+        uses_netloc.insert(0, 'ssh') # Mercurial
+        uses_netloc.insert(0, 'git')
+    (scheme, netloc, path, params, query, fragment) = urlparse(args[0])
+    return bool(scheme and netloc)
+$$;
+
+
+COMMENT ON FUNCTION valid_absolute_url(text) IS 'Ensure the given test is a valid absolute URL, containing both protocol and network location';
+
+
+CREATE FUNCTION valid_branch_name(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    import re
+    name = args[0]
+    pat = r"^(?i)[a-z0-9][a-z0-9+\.\-@_]*\Z"
+    if re.match(pat, name):
+        return 1
+    return 0
+$$;
+
+
+COMMENT ON FUNCTION valid_branch_name(text) IS 'validate a branch name.
+
+    As per valid_name, except we allow uppercase and @';
+
+
+CREATE FUNCTION valid_cve(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $_$
+    import re
+    name = args[0]
+    pat = r"^(19|20)\d{2}-\d{4}$"
+    if re.match(pat, name):
+        return 1
+    return 0
+$_$;
+
+
+COMMENT ON FUNCTION valid_cve(text) IS 'validate a common vulnerability number as defined on www.cve.mitre.org, minus the CAN- or CVE- prefix.';
+
+
+CREATE FUNCTION valid_debian_version(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $_$
+    import re
+    m = re.search("""^(?ix)
+        ([0-9]+:)?
+        ([0-9a-z][a-z0-9+:.~-]*?)
+        (-[a-z0-9+.~]+)?
+        $""", args[0])
+    if m is None:
+        return 0
+    epoch, version, revision = m.groups()
+    if not epoch:
+        # Can''t contain : if no epoch
+        if ":" in version:
+            return 0
+    if not revision:
+        # Can''t contain - if no revision
+        if "-" in version:
+            return 0
+    return 1
+$_$;
+
+
+COMMENT ON FUNCTION valid_debian_version(text) IS 'validate a version number as per Debian Policy';
+
+
+CREATE FUNCTION valid_fingerprint(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    import re
+    if re.match(r"[\dA-F]{40}", args[0]) is not None:
+        return 1
+    else:
+        return 0
+$$;
+
+
+COMMENT ON FUNCTION valid_fingerprint(text) IS 'Returns true if passed a valid GPG fingerprint. Valid GPG fingerprints are a 40 character long hexadecimal number in uppercase.';
+
+
+CREATE FUNCTION valid_keyid(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    import re
+    if re.match(r"[\dA-F]{8}", args[0]) is not None:
+        return 1
+    else:
+        return 0
+$$;
+
+
+COMMENT ON FUNCTION valid_keyid(text) IS 'Returns true if passed a valid GPG keyid. Valid GPG keyids are an 8 character long hexadecimal number in uppercase (in reality, they are 16 characters long but we are using the ''common'' definition.';
+
+
+CREATE FUNCTION valid_regexp(text) RETURNS boolean
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    import re
+    try:
+        re.compile(args[0])
+    except:
+        return False
+    else:
+        return True
+$$;
+
+
+COMMENT ON FUNCTION valid_regexp(text) IS 'Returns true if the input can be compiled as a regular expression.';
+
+
+CREATE FUNCTION version_sort_key(version text) RETURNS text
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $$
+    # If this method is altered, then any functional indexes using it
+    # need to be rebuilt.
+    import re
+
+    [version] = args
+
+    def substitute_filled_numbers(match):
+        # Prepend "~" so that version numbers will show up first
+        # when sorted descending, i.e. [3, 2c, 2b, 1, c, b, a] instead
+        # of [c, b, a, 3, 2c, 2b, 1]. "~" has the highest ASCII value
+        # of visible ASCII characters.
+        return '~' + match.group(0).zfill(5)
+
+    return re.sub(u'\d+', substitute_filled_numbers, version)
+$$;
+
+
+COMMENT ON FUNCTION version_sort_key(version text) IS 'Sort a field as version numbers that do not necessarily conform to debian package versions (For example, when "2-2" should be considered greater than "1:1"). debversion_sort_key() should be used for debian versions. Numbers will be sorted after letters unlike typical ASCII, so that a descending sort will put the latest version number that starts with a number instead of a letter will be at the top. E.g. ascending is [a, z, 1, 9] and descending is [9, 1, z, a].';
+
+
+CREATE FUNCTION you_are_your_own_member() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+    BEGIN
+        INSERT INTO TeamParticipation (person, team)
+            VALUES (NEW.id, NEW.id);
+        RETURN NULL;
+    END;
+$$;
+
+
+COMMENT ON FUNCTION you_are_your_own_member() IS 'Trigger function to ensure that every row added to the Person table gets a corresponding row in the TeamParticipation table, as per the TeamParticipationUsage page on the Launchpad wiki';
+
+
+SET search_path = ts2, pg_catalog;
+
+CREATE FUNCTION _ftq(text) RETURNS text
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $_$
+        import re
+
+        # I think this method would be more robust if we used a real
+        # tokenizer and parser to generate the query string, but we need
+        # something suitable for use as a stored procedure which currently
+        # means no external dependancies.
+
+        # Convert to Unicode
+        query = args[0].decode('utf8')
+        ## plpy.debug('1 query is %s' % repr(query))
+
+        # Normalize whitespace
+        query = re.sub("(?u)\s+"," ", query)
+
+        # Convert AND, OR, NOT and - to tsearch2 punctuation
+        query = re.sub(r"(?u)(?:^|\s)-([\w\(])", r" !\1", query)
+        query = re.sub(r"(?u)\bAND\b", "&", query)
+        query = re.sub(r"(?u)\bOR\b", "|", query)
+        query = re.sub(r"(?u)\bNOT\b", " !", query)
+        ## plpy.debug('2 query is %s' % repr(query))
+
+        # Deal with unwanted punctuation. We convert strings of punctuation
+        # inside words to a '-' character for the hypenation handling below
+        # to deal with further. Outside of words we replace with whitespace.
+        # We don't mess with -&|!()' as they are handled later.
+        #punctuation = re.escape(r'`~@#$%^*+=[]{}:;"<>,.?\/')
+        punctuation = r"[^\w\s\-\&\|\!\(\)']"
+        query = re.sub(r"(?u)(\w)%s+(\w)" % (punctuation,), r"\1-\2", query)
+        query = re.sub(r"(?u)%s+" % (punctuation,), " ", query)
+        ## plpy.debug('3 query is %s' % repr(query))
+
+        # Strip ! characters inside and at the end of a word
+        query = re.sub(r"(?u)(?<=\w)[\!]+", " ", query)
+
+        # Now that we have handle case sensitive booleans, convert to lowercase
+        query = query.lower()
+
+        # Convert foo-bar to ((foo&bar)|foobar) and foo-bar-baz to
+        # ((foo&bar&baz)|foobarbaz)
+        def hyphen_repl(match):
+            bits = match.group(0).split("-")
+            return "((%s)|%s)" % ("&".join(bits), "".join(bits))
+        query = re.sub(r"(?u)\b\w+-[\w\-]+\b", hyphen_repl, query)
+        ## plpy.debug('4 query is %s' % repr(query))
+
+        # Any remaining - characters are spurious
+        query = query.replace('-','')
+
+        # Remove unpartnered bracket on the left and right
+        query = re.sub(r"(?ux) ^ ( [^(]* ) \)", r"(\1)", query)
+        query = re.sub(r"(?ux) \( ( [^)]* ) $", r"(\1)", query)
+
+        # Remove spurious brackets
+        query = re.sub(r"(?u)\(([^\&\|]*?)\)", r" \1 ", query)
+        ## plpy.debug('5 query is %s' % repr(query))
+
+        # Insert & between tokens without an existing boolean operator
+        # ( not proceeded by (|&!
+        query = re.sub(r"(?u)(?<![\(\|\&\!])\s*\(", "&(", query)
+        ## plpy.debug('6 query is %s' % repr(query))
+        # ) not followed by )|&
+        query = re.sub(r"(?u)\)(?!\s*(\)|\||\&|\s*$))", ")&", query)
+        ## plpy.debug('6.1 query is %s' % repr(query))
+        # Whitespace not proceded by (|&! not followed by &|
+        query = re.sub(r"(?u)(?<![\(\|\&\!\s])\s+(?![\&\|\s])", "&", query)
+        ## plpy.debug('7 query is %s' % repr(query))
+
+        # Detect and repair syntax errors - we are lenient because
+        # this input is generally from users.
+
+        # Fix unbalanced brackets
+        openings = query.count("(")
+        closings = query.count(")")
+        if openings > closings:
+            query = query + " ) "*(openings-closings)
+        elif closings > openings:
+            query = " ( "*(closings-openings) + query
+        ## plpy.debug('8 query is %s' % repr(query))
+
+        # Strip ' character that do not have letters on both sides
+        query = re.sub(r"(?u)((?<!\w)'|'(?!\w))", "", query)
+
+        # Brackets containing nothing but whitespace and booleans, recursive
+        last = ""
+        while last != query:
+            last = query
+            query = re.sub(r"(?u)\([\s\&\|\!]*\)", "", query)
+        ## plpy.debug('9 query is %s' % repr(query))
+
+        # An & or | following a (
+        query = re.sub(r"(?u)(?<=\()[\&\|\s]+", "", query)
+        ## plpy.debug('10 query is %s' % repr(query))
+
+        # An &, | or ! immediatly before a )
+        query = re.sub(r"(?u)[\&\|\!\s]*[\&\|\!]+\s*(?=\))", "", query)
+        ## plpy.debug('11 query is %s' % repr(query))
+
+        # An &,| or ! followed by another boolean.
+        query = re.sub(r"(?ux) \s* ( [\&\|\!] ) [\s\&\|]+", r"\1", query)
+        ## plpy.debug('12 query is %s' % repr(query))
+
+        # Leading & or |
+        query = re.sub(r"(?u)^[\s\&\|]+", "", query)
+        ## plpy.debug('13 query is %s' % repr(query))
+
+        # Trailing &, | or !
+        query = re.sub(r"(?u)[\&\|\!\s]+$", "", query)
+        ## plpy.debug('14 query is %s' % repr(query))
+
+        # If we have nothing but whitespace and tsearch2 operators,
+        # return NULL.
+        if re.search(r"(?u)^[\&\|\!\s\(\)]*$", query) is not None:
+            return None
+
+        # Convert back to UTF-8
+        query = query.encode('utf8')
+        ## plpy.debug('15 query is %s' % repr(query))
+        
+        return query or None
+        $_$;
+
+
+CREATE FUNCTION _get_parser_from_curcfg() RETURNS text
+    LANGUAGE sql IMMUTABLE STRICT
+    AS $$select prsname::text from pg_catalog.pg_ts_parser p join pg_ts_config c on cfgparser = p.oid where c.oid = show_curcfg();$$;
+
+
+CREATE FUNCTION concat(pg_catalog.tsvector, pg_catalog.tsvector) RETURNS pg_catalog.tsvector
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsvector_concat$$;
+
+
+CREATE FUNCTION dex_init(internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_dex_init';
+
+
+CREATE FUNCTION dex_lexize(internal, internal, integer) RETURNS internal
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_dex_lexize';
+
+
+CREATE FUNCTION ftiupdate() RETURNS trigger
+    LANGUAGE plpythonu
+    AS $_$
+    new = TD["new"]
+    args = TD["args"][:]
+
+    # Short circuit if none of the relevant columns have been
+    # modified and fti is not being set to NULL (setting the fti
+    # column to NULL is thus how we can force a rebuild of the fti
+    # column).
+    if TD["event"] == "UPDATE" and new["fti"] != None:
+        old = TD["old"]
+        relevant_modification = False
+        for column_name in args[::2]:
+            if new[column_name] != old[column_name]:
+                relevant_modification = True
+                break
+        if not relevant_modification:
+            return "OK"
+
+    # Generate an SQL statement that turns the requested
+    # column values into a weighted tsvector
+    sql = []
+    for i in range(0, len(args), 2):
+        sql.append(
+                "ts2.setweight(ts2.to_tsvector('default', coalesce("
+                "substring(ltrim($%d) from 1 for 2500),'')),"
+                "CAST($%d AS \"char\"))" % (i + 1, i + 2))
+        args[i] = new[args[i]]
+
+    sql = "SELECT %s AS fti" % "||".join(sql)
+
+    # Execute and store in the fti column
+    plan = plpy.prepare(sql, ["text", "char"] * (len(args)/2))
+    new["fti"] = plpy.execute(plan, args, 1)[0]["fti"]
+
+    # Tell PostgreSQL we have modified the data
+    return "MODIFY"
+$_$;
+
+
+COMMENT ON FUNCTION ftiupdate() IS 'Trigger function that keeps the fti tsvector column up to date.';
+
+
+CREATE FUNCTION ftq(text) RETURNS pg_catalog.tsquery
+    LANGUAGE plpythonu IMMUTABLE STRICT
+    AS $_$
+        import re
+
+        # I think this method would be more robust if we used a real
+        # tokenizer and parser to generate the query string, but we need
+        # something suitable for use as a stored procedure which currently
+        # means no external dependancies.
+
+        # Convert to Unicode
+        query = args[0].decode('utf8')
+        ## plpy.debug('1 query is %s' % repr(query))
+
+        # Normalize whitespace
+        query = re.sub("(?u)\s+"," ", query)
+
+        # Convert AND, OR, NOT and - to tsearch2 punctuation
+        query = re.sub(r"(?u)(?:^|\s)-([\w\(])", r" !\1", query)
+        query = re.sub(r"(?u)\bAND\b", "&", query)
+        query = re.sub(r"(?u)\bOR\b", "|", query)
+        query = re.sub(r"(?u)\bNOT\b", " !", query)
+        ## plpy.debug('2 query is %s' % repr(query))
+
+        # Deal with unwanted punctuation. We convert strings of punctuation
+        # inside words to a '-' character for the hypenation handling below
+        # to deal with further. Outside of words we replace with whitespace.
+        # We don't mess with -&|!()' as they are handled later.
+        #punctuation = re.escape(r'`~@#$%^*+=[]{}:;"<>,.?\/')
+        punctuation = r"[^\w\s\-\&\|\!\(\)']"
+        query = re.sub(r"(?u)(\w)%s+(\w)" % (punctuation,), r"\1-\2", query)
+        query = re.sub(r"(?u)%s+" % (punctuation,), " ", query)
+        ## plpy.debug('3 query is %s' % repr(query))
+
+        # Strip ! characters inside and at the end of a word
+        query = re.sub(r"(?u)(?<=\w)[\!]+", " ", query)
+
+        # Now that we have handle case sensitive booleans, convert to lowercase
+        query = query.lower()
+
+        # Convert foo-bar to ((foo&bar)|foobar) and foo-bar-baz to
+        # ((foo&bar&baz)|foobarbaz)
+        def hyphen_repl(match):
+            bits = match.group(0).split("-")
+            return "((%s)|%s)" % ("&".join(bits), "".join(bits))
+        query = re.sub(r"(?u)\b\w+-[\w\-]+\b", hyphen_repl, query)
+        ## plpy.debug('4 query is %s' % repr(query))
+
+        # Any remaining - characters are spurious
+        query = query.replace('-','')
+
+        # Remove unpartnered bracket on the left and right
+        query = re.sub(r"(?ux) ^ ( [^(]* ) \)", r"(\1)", query)
+        query = re.sub(r"(?ux) \( ( [^)]* ) $", r"(\1)", query)
+
+        # Remove spurious brackets
+        query = re.sub(r"(?u)\(([^\&\|]*?)\)", r" \1 ", query)
+        ## plpy.debug('5 query is %s' % repr(query))
+
+        # Insert & between tokens without an existing boolean operator
+        # ( not proceeded by (|&!
+        query = re.sub(r"(?u)(?<![\(\|\&\!])\s*\(", "&(", query)
+        ## plpy.debug('6 query is %s' % repr(query))
+        # ) not followed by )|&
+        query = re.sub(r"(?u)\)(?!\s*(\)|\||\&|\s*$))", ")&", query)
+        ## plpy.debug('6.1 query is %s' % repr(query))
+        # Whitespace not proceded by (|&! not followed by &|
+        query = re.sub(r"(?u)(?<![\(\|\&\!\s])\s+(?![\&\|\s])", "&", query)
+        ## plpy.debug('7 query is %s' % repr(query))
+
+        # Detect and repair syntax errors - we are lenient because
+        # this input is generally from users.
+
+        # Fix unbalanced brackets
+        openings = query.count("(")
+        closings = query.count(")")
+        if openings > closings:
+            query = query + " ) "*(openings-closings)
+        elif closings > openings:
+            query = " ( "*(closings-openings) + query
+        ## plpy.debug('8 query is %s' % repr(query))
+
+        # Strip ' character that do not have letters on both sides
+        query = re.sub(r"(?u)((?<!\w)'|'(?!\w))", "", query)
+
+        # Brackets containing nothing but whitespace and booleans, recursive
+        last = ""
+        while last != query:
+            last = query
+            query = re.sub(r"(?u)\([\s\&\|\!]*\)", "", query)
+        ## plpy.debug('9 query is %s' % repr(query))
+
+        # An & or | following a (
+        query = re.sub(r"(?u)(?<=\()[\&\|\s]+", "", query)
+        ## plpy.debug('10 query is %s' % repr(query))
+
+        # An &, | or ! immediatly before a )
+        query = re.sub(r"(?u)[\&\|\!\s]*[\&\|\!]+\s*(?=\))", "", query)
+        ## plpy.debug('11 query is %s' % repr(query))
+
+        # An &,| or ! followed by another boolean.
+        query = re.sub(r"(?ux) \s* ( [\&\|\!] ) [\s\&\|]+", r"\1", query)
+        ## plpy.debug('12 query is %s' % repr(query))
+
+        # Leading & or |
+        query = re.sub(r"(?u)^[\s\&\|]+", "", query)
+        ## plpy.debug('13 query is %s' % repr(query))
+
+        # Trailing &, | or !
+        query = re.sub(r"(?u)[\&\|\!\s]+$", "", query)
+        ## plpy.debug('14 query is %s' % repr(query))
+
+        # If we have nothing but whitespace and tsearch2 operators,
+        # return NULL.
+        if re.search(r"(?u)^[\&\|\!\s\(\)]*$", query) is not None:
+            return None
+
+        # Convert back to UTF-8
+        query = query.encode('utf8')
+        ## plpy.debug('15 query is %s' % repr(query))
+        
+        p = plpy.prepare("SELECT to_tsquery('default', $1) AS x", ["text"])
+        query = plpy.execute(p, [query], 1)[0]["x"]
+        return query or None
+        $_$;
+
+
+COMMENT ON FUNCTION ftq(text) IS 'Convert a string to an unparsed tsearch2 query';
+
+
+CREATE FUNCTION get_covers(pg_catalog.tsvector, pg_catalog.tsquery) RETURNS text
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_get_covers';
+
+
+CREATE FUNCTION headline(oid, text, pg_catalog.tsquery, text) RETURNS text
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_headline_byid_opt$$;
+
+
+CREATE FUNCTION headline(oid, text, pg_catalog.tsquery) RETURNS text
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_headline_byid$$;
+
+
+CREATE FUNCTION headline(text, text, pg_catalog.tsquery, text) RETURNS text
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/tsearch2', 'tsa_headline_byname';
+
+
+CREATE FUNCTION headline(text, text, pg_catalog.tsquery) RETURNS text
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/tsearch2', 'tsa_headline_byname';
+
+
+CREATE FUNCTION headline(text, pg_catalog.tsquery, text) RETURNS text
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_headline_opt$$;
+
+
+CREATE FUNCTION headline(text, pg_catalog.tsquery) RETURNS text
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_headline$$;
+
+
+CREATE FUNCTION length(pg_catalog.tsvector) RETURNS integer
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsvector_length$$;
+
+
+CREATE FUNCTION lexize(oid, text) RETURNS text[]
+    LANGUAGE internal STRICT
+    AS $$ts_lexize$$;
+
+
+CREATE FUNCTION lexize(text, text) RETURNS text[]
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_lexize_byname';
+
+
+CREATE FUNCTION lexize(text) RETURNS text[]
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_lexize_bycurrent';
+
+
+CREATE FUNCTION numnode(pg_catalog.tsquery) RETURNS integer
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsquery_numnode$$;
+
+
+CREATE FUNCTION parse(oid, text) RETURNS SETOF tokenout
+    LANGUAGE internal STRICT
+    AS $$ts_parse_byid$$;
+
+
+CREATE FUNCTION parse(text, text) RETURNS SETOF tokenout
+    LANGUAGE internal STRICT
+    AS $$ts_parse_byname$$;
+
+
+CREATE FUNCTION parse(text) RETURNS SETOF tokenout
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_parse_current';
+
+
+CREATE FUNCTION plainto_tsquery(oid, text) RETURNS pg_catalog.tsquery
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$plainto_tsquery_byid$$;
+
+
+CREATE FUNCTION plainto_tsquery(text, text) RETURNS pg_catalog.tsquery
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/tsearch2', 'tsa_plainto_tsquery_name';
+
+
+CREATE FUNCTION plainto_tsquery(text) RETURNS pg_catalog.tsquery
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$plainto_tsquery$$;
+
+
+CREATE FUNCTION prsd_end(internal) RETURNS void
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_prsd_end';
+
+
+CREATE FUNCTION prsd_getlexeme(internal, internal, internal) RETURNS integer
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_prsd_getlexeme';
+
+
+CREATE FUNCTION prsd_headline(internal, internal, internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_prsd_headline';
+
+
+CREATE FUNCTION prsd_lextype(internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_prsd_lextype';
+
+
+CREATE FUNCTION prsd_start(internal, integer) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_prsd_start';
+
+
+CREATE FUNCTION querytree(pg_catalog.tsquery) RETURNS text
+    LANGUAGE internal STRICT
+    AS $$tsquerytree$$;
+
+
+CREATE FUNCTION rank(real[], pg_catalog.tsvector, pg_catalog.tsquery) RETURNS real
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_rank_wtt$$;
+
+
+CREATE FUNCTION rank(real[], pg_catalog.tsvector, pg_catalog.tsquery, integer) RETURNS real
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_rank_wttf$$;
+
+
+CREATE FUNCTION rank(pg_catalog.tsvector, pg_catalog.tsquery) RETURNS real
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_rank_tt$$;
+
+
+CREATE FUNCTION rank(pg_catalog.tsvector, pg_catalog.tsquery, integer) RETURNS real
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_rank_ttf$$;
+
+
+CREATE FUNCTION rank_cd(real[], pg_catalog.tsvector, pg_catalog.tsquery) RETURNS real
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_rankcd_wtt$$;
+
+
+CREATE FUNCTION rank_cd(real[], pg_catalog.tsvector, pg_catalog.tsquery, integer) RETURNS real
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_rankcd_wttf$$;
+
+
+CREATE FUNCTION rank_cd(pg_catalog.tsvector, pg_catalog.tsquery) RETURNS real
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_rankcd_tt$$;
+
+
+CREATE FUNCTION rank_cd(pg_catalog.tsvector, pg_catalog.tsquery, integer) RETURNS real
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$ts_rankcd_ttf$$;
+
+
+CREATE FUNCTION reset_tsearch() RETURNS void
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_reset_tsearch';
+
+
+CREATE FUNCTION rewrite(pg_catalog.tsquery, text) RETURNS pg_catalog.tsquery
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsquery_rewrite_query$$;
+
+
+CREATE FUNCTION rewrite(pg_catalog.tsquery, pg_catalog.tsquery, pg_catalog.tsquery) RETURNS pg_catalog.tsquery
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsquery_rewrite$$;
+
+
+CREATE FUNCTION rewrite_accum(pg_catalog.tsquery, pg_catalog.tsquery[]) RETURNS pg_catalog.tsquery
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_rewrite_accum';
+
+
+CREATE FUNCTION rewrite_finish(pg_catalog.tsquery) RETURNS pg_catalog.tsquery
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_rewrite_finish';
+
+
+CREATE FUNCTION set_curcfg(integer) RETURNS void
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_set_curcfg';
+
+
+CREATE FUNCTION set_curcfg(text) RETURNS void
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_set_curcfg_byname';
+
+
+CREATE FUNCTION set_curdict(integer) RETURNS void
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_set_curdict';
+
+
+CREATE FUNCTION set_curdict(text) RETURNS void
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_set_curdict_byname';
+
+
+CREATE FUNCTION set_curprs(integer) RETURNS void
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_set_curprs';
+
+
+CREATE FUNCTION set_curprs(text) RETURNS void
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_set_curprs_byname';
+
+
+CREATE FUNCTION setweight(pg_catalog.tsvector, "char") RETURNS pg_catalog.tsvector
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsvector_setweight$$;
+
+
+CREATE FUNCTION show_curcfg() RETURNS oid
+    LANGUAGE internal STABLE STRICT
+    AS $$get_current_ts_config$$;
+
+
+CREATE FUNCTION snb_en_init(internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_snb_en_init';
+
+
+CREATE FUNCTION snb_lexize(internal, internal, integer) RETURNS internal
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_snb_lexize';
+
+
+CREATE FUNCTION snb_ru_init(internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_snb_ru_init';
+
+
+CREATE FUNCTION snb_ru_init_koi8(internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_snb_ru_init_koi8';
+
+
+CREATE FUNCTION snb_ru_init_utf8(internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_snb_ru_init_utf8';
+
+
+CREATE FUNCTION spell_init(internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_spell_init';
+
+
+CREATE FUNCTION spell_lexize(internal, internal, integer) RETURNS internal
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_spell_lexize';
+
+
+CREATE FUNCTION stat(text) RETURNS SETOF statinfo
+    LANGUAGE internal STRICT
+    AS $$ts_stat1$$;
+
+
+CREATE FUNCTION stat(text, text) RETURNS SETOF statinfo
+    LANGUAGE internal STRICT
+    AS $$ts_stat2$$;
+
+
+CREATE FUNCTION strip(pg_catalog.tsvector) RETURNS pg_catalog.tsvector
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsvector_strip$$;
+
+
+CREATE FUNCTION syn_init(internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_syn_init';
+
+
+CREATE FUNCTION syn_lexize(internal, internal, integer) RETURNS internal
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_syn_lexize';
+
+
+CREATE FUNCTION thesaurus_init(internal) RETURNS internal
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_thesaurus_init';
+
+
+CREATE FUNCTION thesaurus_lexize(internal, internal, integer, internal) RETURNS internal
+    LANGUAGE c STRICT
+    AS '$libdir/tsearch2', 'tsa_thesaurus_lexize';
+
+
+CREATE FUNCTION to_tsquery(oid, text) RETURNS pg_catalog.tsquery
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$to_tsquery_byid$$;
+
+
+CREATE FUNCTION to_tsquery(text, text) RETURNS pg_catalog.tsquery
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/tsearch2', 'tsa_to_tsquery_name';
+
+
+CREATE FUNCTION to_tsquery(text) RETURNS pg_catalog.tsquery
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$to_tsquery$$;
+
+
+CREATE FUNCTION to_tsvector(oid, text) RETURNS pg_catalog.tsvector
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$to_tsvector_byid$$;
+
+
+CREATE FUNCTION to_tsvector(text, text) RETURNS pg_catalog.tsvector
+    LANGUAGE c IMMUTABLE STRICT
+    AS '$libdir/tsearch2', 'tsa_to_tsvector_name';
+
+
+CREATE FUNCTION to_tsvector(text) RETURNS pg_catalog.tsvector
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$to_tsvector$$;
+
+
+CREATE FUNCTION token_type(integer) RETURNS SETOF tokentype
+    LANGUAGE internal STRICT ROWS 16
+    AS $$ts_token_type_byid$$;
+
+
+CREATE FUNCTION token_type(text) RETURNS SETOF tokentype
+    LANGUAGE internal STRICT ROWS 16
+    AS $$ts_token_type_byname$$;
+
+
+CREATE FUNCTION token_type() RETURNS SETOF tokentype
+    LANGUAGE c STRICT ROWS 16
+    AS '$libdir/tsearch2', 'tsa_token_type_current';
+
+
+CREATE FUNCTION ts_debug(text) RETURNS SETOF tsdebug
+    LANGUAGE sql STRICT
+    AS $_$
+select
+        (select c.cfgname::text from pg_catalog.pg_ts_config as c
+         where c.oid = show_curcfg()),
+        t.alias as tok_type,
+        t.descr as description,
+        p.token,
+        ARRAY ( SELECT m.mapdict::pg_catalog.regdictionary::pg_catalog.text
+                FROM pg_catalog.pg_ts_config_map AS m
+                WHERE m.mapcfg = show_curcfg() AND m.maptokentype = p.tokid
+                ORDER BY m.mapseqno )
+        AS dict_name,
+        strip(to_tsvector(p.token)) as tsvector
+from
+        parse( _get_parser_from_curcfg(), $1 ) as p,
+        token_type() as t
+where
+        t.tokid = p.tokid
+$_$;
+
+
+CREATE FUNCTION tsearch2() RETURNS trigger
+    LANGUAGE c
+    AS '$libdir/tsearch2', 'tsa_tsearch2';
+
+
+CREATE FUNCTION tsq_mcontained(pg_catalog.tsquery, pg_catalog.tsquery) RETURNS boolean
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsq_mcontained$$;
+
+
+CREATE FUNCTION tsq_mcontains(pg_catalog.tsquery, pg_catalog.tsquery) RETURNS boolean
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsq_mcontains$$;
+
+
+CREATE FUNCTION tsquery_and(pg_catalog.tsquery, pg_catalog.tsquery) RETURNS pg_catalog.tsquery
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsquery_and$$;
+
+
+CREATE FUNCTION tsquery_not(pg_catalog.tsquery) RETURNS pg_catalog.tsquery
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsquery_not$$;
+
+
+CREATE FUNCTION tsquery_or(pg_catalog.tsquery, pg_catalog.tsquery) RETURNS pg_catalog.tsquery
+    LANGUAGE internal IMMUTABLE STRICT
+    AS $$tsquery_or$$;
+
+
+SET search_path = public, pg_catalog;
+
+CREATE OPERATOR > (
+    PROCEDURE = debversion_gt,
+    LEFTARG = debversion,
+    RIGHTARG = debversion,
+    COMMUTATOR = <,
+    NEGATOR = >=
+);
+
+
+COMMENT ON OPERATOR > (debversion, debversion) IS 'debversion greater-than';
+
+
+CREATE AGGREGATE max(debversion) (
+    SFUNC = debversion_larger,
+    STYPE = debversion,
+    SORTOP = >
+);
+
+
+CREATE OPERATOR < (
+    PROCEDURE = debversion_lt,
+    LEFTARG = debversion,
+    RIGHTARG = debversion,
+    COMMUTATOR = >,
+    NEGATOR = >=
+);
+
+
+COMMENT ON OPERATOR < (debversion, debversion) IS 'debversion less-than';
+
+
+CREATE AGGREGATE min(debversion) (
+    SFUNC = debversion_smaller,
+    STYPE = debversion,
+    SORTOP = <
+);
+
+
+SET search_path = ts2, pg_catalog;
+
+CREATE AGGREGATE rewrite(pg_catalog.tsquery[]) (
+    SFUNC = rewrite_accum,
+    STYPE = pg_catalog.tsquery,
+    FINALFUNC = rewrite_finish
+);
+
+
+SET search_path = public, pg_catalog;
+
+CREATE OPERATOR <= (
+    PROCEDURE = debversion_le,
+    LEFTARG = debversion,
+    RIGHTARG = debversion,
+    COMMUTATOR = >=,
+    NEGATOR = >
+);
+
+
+COMMENT ON OPERATOR <= (debversion, debversion) IS 'debversion less-than-or-equal';
+
+
+CREATE OPERATOR <> (
+    PROCEDURE = debversion_ne,
+    LEFTARG = debversion,
+    RIGHTARG = debversion,
+    COMMUTATOR = <>,
+    NEGATOR = =
+);
+
+
+COMMENT ON OPERATOR <> (debversion, debversion) IS 'debversion not equal';
+
+
+CREATE OPERATOR = (
+    PROCEDURE = debversion_eq,
+    LEFTARG = debversion,
+    RIGHTARG = debversion,
+    COMMUTATOR = =,
+    NEGATOR = <>
+);
+
+
+COMMENT ON OPERATOR = (debversion, debversion) IS 'debversion equal';
+
+
+CREATE OPERATOR >= (
+    PROCEDURE = debversion_ge,
+    LEFTARG = debversion,
+    RIGHTARG = debversion,
+    COMMUTATOR = <=,
+    NEGATOR = <
+);
+
+
+COMMENT ON OPERATOR >= (debversion, debversion) IS 'debversion greater-than-or-equal';
+
+
+CREATE OPERATOR FAMILY debversion_ops USING btree;
+
+
+CREATE OPERATOR CLASS debversion_ops
+    DEFAULT FOR TYPE debversion USING btree AS
+    OPERATOR 1 <(debversion,debversion) ,
+    OPERATOR 2 <=(debversion,debversion) ,
+    OPERATOR 3 =(debversion,debversion) ,
+    OPERATOR 4 >=(debversion,debversion) ,
+    OPERATOR 5 >(debversion,debversion) ,
+    FUNCTION 1 debversion_cmp(debversion,debversion);
+
+
+CREATE OPERATOR FAMILY debversion_ops USING hash;
+
+
+CREATE OPERATOR CLASS debversion_ops
+    DEFAULT FOR TYPE debversion USING hash AS
+    OPERATOR 1 =(debversion,debversion) ,
+    FUNCTION 1 debversion_hash(debversion);
+
+
+SET search_path = ts2, pg_catalog;
+
+CREATE OPERATOR FAMILY tsquery_ops USING btree;
+
+
+CREATE OPERATOR CLASS tsquery_ops
+    FOR TYPE pg_catalog.tsquery USING btree AS
+    OPERATOR 1 <(pg_catalog.tsquery,pg_catalog.tsquery) ,
+    OPERATOR 2 <=(pg_catalog.tsquery,pg_catalog.tsquery) ,
+    OPERATOR 3 =(pg_catalog.tsquery,pg_catalog.tsquery) ,
+    OPERATOR 4 >=(pg_catalog.tsquery,pg_catalog.tsquery) ,
+    OPERATOR 5 >(pg_catalog.tsquery,pg_catalog.tsquery) ,
+    FUNCTION 1 tsquery_cmp(pg_catalog.tsquery,pg_catalog.tsquery);
+
+
+CREATE OPERATOR FAMILY tsvector_ops USING btree;
+
+
+CREATE OPERATOR CLASS tsvector_ops
+    FOR TYPE pg_catalog.tsvector USING btree AS
+    OPERATOR 1 <(pg_catalog.tsvector,pg_catalog.tsvector) ,
+    OPERATOR 2 <=(pg_catalog.tsvector,pg_catalog.tsvector) ,
+    OPERATOR 3 =(pg_catalog.tsvector,pg_catalog.tsvector) ,
+    OPERATOR 4 >=(pg_catalog.tsvector,pg_catalog.tsvector) ,
+    OPERATOR 5 >(pg_catalog.tsvector,pg_catalog.tsvector) ,
+    FUNCTION 1 tsvector_cmp(pg_catalog.tsvector,pg_catalog.tsvector);
+
+
+SET search_path = pg_catalog;
+
+CREATE CAST (character AS public.debversion) WITH FUNCTION public.debversion(character);
+
+
+CREATE CAST (public.debversion AS character) WITHOUT FUNCTION AS ASSIGNMENT;
+
+
+CREATE CAST (public.debversion AS text) WITHOUT FUNCTION AS IMPLICIT;
+
+
+CREATE CAST (public.debversion AS character varying) WITHOUT FUNCTION AS IMPLICIT;
+
+
+CREATE CAST (text AS public.debversion) WITHOUT FUNCTION AS ASSIGNMENT;
+
+
+CREATE CAST (character varying AS public.debversion) WITHOUT FUNCTION AS ASSIGNMENT;
+
+
+SET search_path = ts2, pg_catalog;
+
+CREATE TEXT SEARCH CONFIGURATION "default" (
+    PARSER = pg_catalog."default" );
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR asciiword WITH english_stem;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR word WITH english_stem;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR numword WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR email WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR url WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR host WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR sfloat WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR version WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR hword_numpart WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR hword_part WITH english_stem;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR hword_asciipart WITH english_stem;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR numhword WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR asciihword WITH english_stem;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR hword WITH english_stem;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR url_path WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR file WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR "float" WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR "int" WITH simple;
+
+ALTER TEXT SEARCH CONFIGURATION "default"
+    ADD MAPPING FOR uint WITH simple;
+
+
+SET search_path = public, pg_catalog;
+
+CREATE TABLE accesspolicy (
+    id integer NOT NULL,
+    product integer,
+    distribution integer,
+    type integer NOT NULL,
+    CONSTRAINT has_target CHECK (((product IS NULL) <> (distribution IS NULL)))
+);
+
+
+CREATE SEQUENCE accesspolicy_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE accesspolicy_id_seq OWNED BY accesspolicy.id;
+
+
+CREATE TABLE accesspolicyartifact (
+    id integer NOT NULL,
+    bug integer,
+    branch integer,
+    policy integer,
+    CONSTRAINT has_artifact CHECK (((bug IS NULL) <> (branch IS NULL)))
+);
+
+
+CREATE SEQUENCE accesspolicyartifact_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE accesspolicyartifact_id_seq OWNED BY accesspolicyartifact.id;
+
+
+CREATE TABLE accesspolicygrant (
+    id integer NOT NULL,
+    grantee integer NOT NULL,
+    grantor integer NOT NULL,
+    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
+    policy integer,
+    artifact integer,
+    CONSTRAINT has_target CHECK (((policy IS NULL) <> (artifact IS NULL)))
+);
+
+
+CREATE SEQUENCE accesspolicygrant_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE accesspolicygrant_id_seq OWNED BY accesspolicygrant.id;
+
 
 CREATE TABLE account (
     id integer NOT NULL,
@@ -63,36 +4563,64 @@
     status integer NOT NULL,
     date_status_set timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     displayname text NOT NULL,
-    openid_identifier text DEFAULT generate_openid_identifier() NOT NULL,
-    status_comment text,
-    old_openid_identifier text
+    status_comment text
 );
 
+
+COMMENT ON TABLE account IS 'An account that may be used for authenticating to Canonical or other systems.';
+
+
+COMMENT ON COLUMN account.status IS 'The status of the account.';
+
+
+COMMENT ON COLUMN account.date_status_set IS 'When the status was last changed.';
+
+
+COMMENT ON COLUMN account.displayname IS 'Name to display when rendering information about this account.';
+
+
+COMMENT ON COLUMN account.status_comment IS 'The comment on the status of the account.';
+
+
 CREATE SEQUENCE account_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE account_id_seq OWNED BY account.id;
 
+
 CREATE TABLE accountpassword (
     id integer NOT NULL,
     account integer NOT NULL,
     password text NOT NULL
 );
 
+
+COMMENT ON TABLE accountpassword IS 'A password used to authenticate an Account.';
+
+
+COMMENT ON COLUMN accountpassword.password IS 'SSHA digest encrypted password.';
+
+
 CREATE SEQUENCE accountpassword_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE accountpassword_id_seq OWNED BY accountpassword.id;
 
+
 CREATE VIEW alllocks AS
     SELECT a.procpid, a.usename, (now() - a.query_start) AS age, c.relname, l.mode, l.granted, a.current_query FROM ((pg_locks l JOIN pg_class c ON ((l.relation = c.oid))) LEFT JOIN pg_stat_activity a ON ((a.procpid = l.pid)));
 
+
 CREATE TABLE announcement (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -110,14 +4638,30 @@
     CONSTRAINT valid_url CHECK (valid_absolute_url(url))
 );
 
+
+COMMENT ON TABLE announcement IS 'A project announcement. This is a single item of news or information that the project is communicating. Announcements can be attached to a Project, a Product or a Distribution.';
+
+
+COMMENT ON COLUMN announcement.date_announced IS 'The date at which an announcement will become public, if it is active. If this is not set then the announcement will not become public until someone consciously publishes it (which sets this date).';
+
+
+COMMENT ON COLUMN announcement.url IS 'A web location for the announcement itself.';
+
+
+COMMENT ON COLUMN announcement.active IS 'Whether or not the announcement is public. This is TRUE by default, but can be set to FALSE if the project "retracts" the announcement.';
+
+
 CREATE SEQUENCE announcement_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE announcement_id_seq OWNED BY announcement.id;
 
+
 CREATE TABLE answercontact (
     id integer NOT NULL,
     product integer,
@@ -128,14 +4672,36 @@
     CONSTRAINT valid_target CHECK ((((product IS NULL) <> (distribution IS NULL)) AND ((product IS NULL) OR (sourcepackagename IS NULL))))
 );
 
+
+COMMENT ON TABLE answercontact IS 'Defines the answer contact for a given question target. The answer contact will be automatically notified about changes to any questions filed on the question target.';
+
+
+COMMENT ON COLUMN answercontact.product IS 'The product that the answer contact supports.';
+
+
+COMMENT ON COLUMN answercontact.distribution IS 'The distribution that the answer contact supports.';
+
+
+COMMENT ON COLUMN answercontact.sourcepackagename IS 'The sourcepackagename that the answer contact supports.';
+
+
+COMMENT ON COLUMN answercontact.person IS 'The person or team associated with the question target.';
+
+
+COMMENT ON COLUMN answercontact.date_created IS 'The date the answer contact was submitted.';
+
+
 CREATE SEQUENCE answercontact_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE answercontact_id_seq OWNED BY answercontact.id;
 
+
 CREATE TABLE apportjob (
     id integer NOT NULL,
     job integer NOT NULL,
@@ -144,14 +4710,30 @@
     json_data text
 );
 
+
+COMMENT ON TABLE apportjob IS 'Contains references to jobs to be run against Apport BLOBs.';
+
+
+COMMENT ON COLUMN apportjob.blob IS 'The TemporaryBlobStorage entry on which the job is to be run.';
+
+
+COMMENT ON COLUMN apportjob.job_type IS 'The type of job (enumeration value). Allows us to query the database for a given subset of ApportJobs.';
+
+
+COMMENT ON COLUMN apportjob.json_data IS 'A JSON struct containing data for the job.';
+
+
 CREATE SEQUENCE apportjob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE apportjob_id_seq OWNED BY apportjob.id;
 
+
 CREATE TABLE archive (
     id integer NOT NULL,
     owner integer NOT NULL,
@@ -189,28 +4771,132 @@
     CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
+
+COMMENT ON TABLE archive IS 'A package archive. Commonly either a distribution''s main_archive or a ppa''s archive.';
+
+
+COMMENT ON COLUMN archive.owner IS 'Identifies the PPA owner when it has one.';
+
+
+COMMENT ON COLUMN archive.description IS 'Allow users to describe their PPAs content.';
+
+
+COMMENT ON COLUMN archive.enabled IS 'Whether or not the PPA is enabled for accepting uploads.';
+
+
+COMMENT ON COLUMN archive.authorized_size IS 'Size, in MiB, allowed for this PPA.';
+
+
+COMMENT ON COLUMN archive.distribution IS 'The distribution that uses this archive.';
+
+
+COMMENT ON COLUMN archive.purpose IS 'The purpose of this archive, e.g. COMMERCIAL.  See the ArchivePurpose DBSchema item.';
+
+
+COMMENT ON COLUMN archive.private IS 'Whether or not the archive is private. This affects the global visibility of the archive.';
+
+
+COMMENT ON COLUMN archive.sources_cached IS 'Number of sources already cached for this archive.';
+
+
+COMMENT ON COLUMN archive.binaries_cached IS 'Number of binaries already cached for this archive.';
+
+
+COMMENT ON COLUMN archive.package_description_cache IS 'Text blob containing all source and binary names and descriptions concatenated. Used to to build the tsearch indexes on this table.';
+
+
+COMMENT ON COLUMN archive.require_virtualized IS 'Whether this archive has binaries that should be built on a virtual machine, e.g. PPAs';
+
+
+COMMENT ON COLUMN archive.name IS 'The name of the archive.';
+
+
+COMMENT ON COLUMN archive.publish IS 'Whether this archive should be published.';
+
+
+COMMENT ON COLUMN archive.date_updated IS 'When were the rebuild statistics last updated?';
+
+
+COMMENT ON COLUMN archive.total_count IS 'How many source packages are in the rebuild archive altogether?';
+
+
+COMMENT ON COLUMN archive.pending_count IS 'How many packages still need building?';
+
+
+COMMENT ON COLUMN archive.succeeded_count IS 'How many source packages were built sucessfully?';
+
+
+COMMENT ON COLUMN archive.failed_count IS 'How many packages failed to build?';
+
+
+COMMENT ON COLUMN archive.building_count IS 'How many packages are building at present?';
+
+
+COMMENT ON COLUMN archive.signing_key IS 'The GpgKey used for signing this archive.';
+
+
+COMMENT ON COLUMN archive.removed_binary_retention_days IS 'The number of days before superseded or deleted binary files are expired in the librarian, or zero for never.';
+
+
+COMMENT ON COLUMN archive.num_old_versions_published IS 'The number of versions of a package to keep published before older versions are superseded.';
+
+
+COMMENT ON COLUMN archive.displayname IS 'User defined displayname for this archive.';
+
+
+COMMENT ON COLUMN archive.relative_build_score IS 'A delta to the build score that is applied to all builds in this archive.';
+
+
+COMMENT ON COLUMN archive.external_dependencies IS 'Newline-separated list of repositories to be used to retrieve any external build dependencies when building packages in this archive, in the format: deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] [components]  The series variable is replaced with the series name of the context build.  This column is specifically and only intended for OEM migration to Launchpad and should be re-examined in October 2010 to see if it is still relevant.';
+
+
+COMMENT ON COLUMN archive.status IS 'The status of this archive, e.g. ACTIVE.  See the ArchiveState DBSchema item.';
+
+
+COMMENT ON COLUMN archive.commercial IS 'Whether this archive is a commercial Archive and should appear in the Software Center.';
+
+
+COMMENT ON COLUMN archive.build_debug_symbols IS 'Whether builds for this archive should create debug symbol packages.';
+
+
 CREATE SEQUENCE archive_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE archive_id_seq OWNED BY archive.id;
 
+
 CREATE TABLE archivearch (
     id integer NOT NULL,
     archive integer NOT NULL,
     processorfamily integer NOT NULL
 );
 
+
+COMMENT ON TABLE archivearch IS 'ArchiveArch: A table that allows a user to specify which architectures an archive requires or supports.';
+
+
+COMMENT ON COLUMN archivearch.archive IS 'The archive for which an architecture is specified.';
+
+
+COMMENT ON COLUMN archivearch.processorfamily IS 'The architecture specified for the archive on hand.';
+
+
 CREATE SEQUENCE archivearch_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE archivearch_id_seq OWNED BY archivearch.id;
 
+
 CREATE TABLE archiveauthtoken (
     id integer NOT NULL,
     archive integer NOT NULL,
@@ -220,14 +4906,36 @@
     token text NOT NULL
 );
 
+
+COMMENT ON TABLE archiveauthtoken IS 'Authorisation tokens to use in .htaccess for published archives.';
+
+
+COMMENT ON COLUMN archiveauthtoken.archive IS 'The archive to which this token refers.';
+
+
+COMMENT ON COLUMN archiveauthtoken.person IS 'The person to which this token applies.';
+
+
+COMMENT ON COLUMN archiveauthtoken.date_created IS 'The date and time this token was created.';
+
+
+COMMENT ON COLUMN archiveauthtoken.date_deactivated IS 'The date and time this token was deactivated.';
+
+
+COMMENT ON COLUMN archiveauthtoken.token IS 'The token text for this authorisation.';
+
+
 CREATE SEQUENCE archiveauthtoken_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE archiveauthtoken_id_seq OWNED BY archiveauthtoken.id;
 
+
 CREATE TABLE archivedependency (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -238,14 +4946,30 @@
     CONSTRAINT distinct_archives CHECK ((archive <> dependency))
 );
 
+
+COMMENT ON TABLE archivedependency IS 'This table maps a given archive to all other archives it should depend on.';
+
+
+COMMENT ON COLUMN archivedependency.date_created IS 'Instant when the dependency was created.';
+
+
+COMMENT ON COLUMN archivedependency.archive IS 'The archive where the dependency should be applied.';
+
+
+COMMENT ON COLUMN archivedependency.dependency IS 'The archive to depend on.';
+
+
 CREATE SEQUENCE archivedependency_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE archivedependency_id_seq OWNED BY archivedependency.id;
 
+
 CREATE TABLE archivejob (
     id integer NOT NULL,
     job integer NOT NULL,
@@ -254,6 +4978,19 @@
     json_data text
 );
 
+
+COMMENT ON TABLE archivejob IS 'Contains references to jobs to be run against Archives.';
+
+
+COMMENT ON COLUMN archivejob.archive IS 'The archive on which the job is to be run.';
+
+
+COMMENT ON COLUMN archivejob.job_type IS 'The type of job (enumeration value). Allows us to query the database for a given subset of ArchiveJobs.';
+
+
+COMMENT ON COLUMN archivejob.json_data IS 'A JSON struct containing data for the job.';
+
+
 CREATE SEQUENCE archivejob_id_seq
     START WITH 1
     INCREMENT BY 1
@@ -261,8 +4998,10 @@
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE archivejob_id_seq OWNED BY archivejob.id;
 
+
 CREATE TABLE archivepermission (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -276,14 +5015,45 @@
     CONSTRAINT one_target CHECK ((null_count(ARRAY[packageset, component, sourcepackagename]) = 2))
 );
 
+
+COMMENT ON TABLE archivepermission IS 'ArchivePermission: A record of who has permission to upload and approve uploads to an archive (and hence a distribution)';
+
+
+COMMENT ON COLUMN archivepermission.date_created IS 'The date that this permission was created.';
+
+
+COMMENT ON COLUMN archivepermission.person IS 'The person or team to whom the permission is being granted.';
+
+
+COMMENT ON COLUMN archivepermission.permission IS 'The permission type being granted.';
+
+
+COMMENT ON COLUMN archivepermission.archive IS 'The archive to which this permission applies.';
+
+
+COMMENT ON COLUMN archivepermission.component IS 'The component to which this upload permission applies.';
+
+
+COMMENT ON COLUMN archivepermission.sourcepackagename IS 'The source package name to which this permission applies.  This can be used to provide package-level permissions to single users.';
+
+
+COMMENT ON COLUMN archivepermission.packageset IS 'The package set to which this permission applies.';
+
+
+COMMENT ON COLUMN archivepermission.explicit IS 'This flag is set for package sets containing high-profile packages that must not break and/or require specialist skills for proper handling e.g. the kernel.';
+
+
 CREATE SEQUENCE archivepermission_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE archivepermission_id_seq OWNED BY archivepermission.id;
 
+
 CREATE TABLE archivesubscriber (
     id integer NOT NULL,
     archive integer NOT NULL,
@@ -297,33 +5067,47 @@
     cancelled_by integer
 );
 
+
+COMMENT ON TABLE archivesubscriber IS 'An authorised person or team subscription to an archive.';
+
+
+COMMENT ON COLUMN archivesubscriber.archive IS 'The archive that the subscriber is authorised to see.';
+
+
+COMMENT ON COLUMN archivesubscriber.registrant IS 'The person who authorised this subscriber.';
+
+
+COMMENT ON COLUMN archivesubscriber.date_created IS 'The date and time this subscription was created.';
+
+
+COMMENT ON COLUMN archivesubscriber.subscriber IS 'The person or team that this subscription refers to.';
+
+
+COMMENT ON COLUMN archivesubscriber.date_expires IS 'The date and time this subscription will expire. If NULL, it does not expire.';
+
+
+COMMENT ON COLUMN archivesubscriber.status IS 'The status of the subscription, e.g. PENDING, ACTIVE, CANCELLING, CANCELLED.';
+
+
+COMMENT ON COLUMN archivesubscriber.description IS 'An optional note for the archive owner to describe the subscription.';
+
+
+COMMENT ON COLUMN archivesubscriber.date_cancelled IS 'The date and time this subscription was revoked.';
+
+
+COMMENT ON COLUMN archivesubscriber.cancelled_by IS 'The person who revoked this subscription.';
+
+
 CREATE SEQUENCE archivesubscriber_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE archivesubscriber_id_seq OWNED BY archivesubscriber.id;
 
-CREATE TABLE authtoken (
-    id integer NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    date_consumed timestamp without time zone,
-    token_type integer NOT NULL,
-    token text NOT NULL,
-    requester integer,
-    requester_email text,
-    email text NOT NULL,
-    redirection_url text
-);
-
-CREATE SEQUENCE authtoken_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE authtoken_id_seq OWNED BY authtoken.id;
 
 CREATE TABLE binarypackagename (
     id integer NOT NULL,
@@ -331,15 +5115,30 @@
     CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
+
+COMMENT ON TABLE binarypackagename IS 'BinaryPackageName: A soyuz binary package name.';
+
+
+COMMENT ON COLUMN binarypackagename.name IS 'A lowercase name identifying one or more binarypackages';
+
+
 CREATE TABLE sourcepackagename (
     id integer NOT NULL,
     name text NOT NULL,
     CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
+
+COMMENT ON TABLE sourcepackagename IS 'SourcePackageName: A soyuz source package name.';
+
+
+COMMENT ON COLUMN sourcepackagename.name IS 'A lowercase name identifying one or more sourcepackages';
+
+
 CREATE VIEW binaryandsourcepackagenameview AS
     SELECT binarypackagename.name FROM binarypackagename UNION SELECT sourcepackagename.name FROM sourcepackagename;
 
+
 CREATE TABLE binarypackagebuild (
     id integer NOT NULL,
     package_build integer NOT NULL,
@@ -347,14 +5146,30 @@
     source_package_release integer NOT NULL
 );
 
+
+COMMENT ON TABLE binarypackagebuild IS 'BinaryPackageBuild: This table links a package build with a distroarchseries and sourcepackagerelease.';
+
+
+COMMENT ON COLUMN binarypackagebuild.package_build IS 'Points to the related package build with the base information.';
+
+
+COMMENT ON COLUMN binarypackagebuild.distro_arch_series IS 'Points the target DistroArchSeries for this build.';
+
+
+COMMENT ON COLUMN binarypackagebuild.source_package_release IS 'SourcePackageRelease which originated this build.';
+
+
 CREATE SEQUENCE binarypackagebuild_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE binarypackagebuild_id_seq OWNED BY binarypackagebuild.id;
 
+
 CREATE TABLE binarypackagefile (
     binarypackagerelease integer NOT NULL,
     libraryfile integer NOT NULL,
@@ -362,14 +5177,30 @@
     id integer DEFAULT nextval(('binarypackagefile_id_seq'::text)::regclass) NOT NULL
 );
 
+
+COMMENT ON TABLE binarypackagefile IS 'BinaryPackageFile: A soyuz <-> librarian link table. This table represents the ownership in the librarian of a file which represents a binary package';
+
+
+COMMENT ON COLUMN binarypackagefile.binarypackagerelease IS 'The binary package which is represented by the file';
+
+
+COMMENT ON COLUMN binarypackagefile.libraryfile IS 'The file in the librarian which represents the package';
+
+
+COMMENT ON COLUMN binarypackagefile.filetype IS 'The "type" of the file. E.g. DEB, RPM';
+
+
 CREATE SEQUENCE binarypackagefile_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE binarypackagefile_id_seq OWNED BY binarypackagefile.id;
 
+
 CREATE TABLE binarypackagepublishinghistory (
     id integer NOT NULL,
     binarypackagerelease integer NOT NULL,
@@ -388,13 +5219,69 @@
     pocket integer DEFAULT 0 NOT NULL,
     archive integer NOT NULL,
     removed_by integer,
-    removal_comment text
+    removal_comment text,
+    binarypackagename integer
 );
 
+
+COMMENT ON TABLE binarypackagepublishinghistory IS 'PackagePublishingHistory: The history of a BinaryPackagePublishing record. This table represents the lifetime of a publishing record from inception to deletion. Records are never removed from here and in time the publishing table may become a view onto this table. A column being NULL indicates there''s no data for that state transition. E.g. a package which is removed without being superseded won''t have datesuperseded or supersededby filled in.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.binarypackagerelease IS 'The binarypackage being published.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.distroarchseries IS 'The distroarchseries into which the binarypackage is being published.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.status IS 'The current status of the publishing.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.component IS 'The component into which the publishing takes place.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.section IS 'The section into which the publishing takes place.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.priority IS 'The priority at which the publishing takes place.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.datecreated IS 'The date/time on which the publishing record was created.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.datepublished IS 'The date/time on which the source was actually published into an archive.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.datesuperseded IS 'The date/time on which the source was superseded by a new source.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.supersededby IS 'The build which superseded this package. This seems odd but it is important because a new build may not actually build a given binarypackage and we need to supersede it appropriately';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.datemadepending IS 'The date/time on which this publishing record was made to be pending removal from the archive.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.scheduleddeletiondate IS 'The date/time at which the package is/was scheduled to be deleted.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.dateremoved IS 'The date/time at which the package was actually deleted.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.pocket IS 'The pocket into which this record is published. The RELEASE pocket (zero) provides behaviour as normal. Other pockets may append things to the distroseries name such as the UPDATES pocket (-updates) or the SECURITY pocket (-security).';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.archive IS 'Target archive for this publishing record.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.removed_by IS 'Person responsible for the removal.';
+
+
+COMMENT ON COLUMN binarypackagepublishinghistory.removal_comment IS 'Reason why the publication was removed.';
+
+
 CREATE TABLE binarypackagerelease (
     id integer NOT NULL,
     binarypackagename integer NOT NULL,
-    version text NOT NULL,
+    version debversion NOT NULL,
     summary text NOT NULL,
     description text NOT NULL,
     build integer NOT NULL,
@@ -418,9 +5305,90 @@
     enhances text,
     breaks text,
     debug_package integer,
-    CONSTRAINT valid_version CHECK (valid_debian_version(version))
+    user_defined_fields text,
+    homepage text,
+    CONSTRAINT valid_version CHECK (valid_debian_version((version)::text))
 );
 
+
+COMMENT ON TABLE binarypackagerelease IS 'BinaryPackageRelease: A soyuz binary package representation. This table stores the records for each binary package uploaded into the system. Each sourcepackagerelease may build various binarypackages on various architectures.';
+
+
+COMMENT ON COLUMN binarypackagerelease.binarypackagename IS 'A reference to the name of the binary package';
+
+
+COMMENT ON COLUMN binarypackagerelease.version IS 'The version of the binary package. E.g. "1.0-2"';
+
+
+COMMENT ON COLUMN binarypackagerelease.summary IS 'A summary of the binary package. Commonly used on listings of binary packages';
+
+
+COMMENT ON COLUMN binarypackagerelease.description IS 'A longer more detailed description of the binary package';
+
+
+COMMENT ON COLUMN binarypackagerelease.build IS 'The build in which this binarypackage was produced';
+
+
+COMMENT ON COLUMN binarypackagerelease.binpackageformat IS 'The binarypackage format. E.g. RPM, DEB etc';
+
+
+COMMENT ON COLUMN binarypackagerelease.component IS 'The archive component that this binarypackage is in. E.g. main, universe etc';
+
+
+COMMENT ON COLUMN binarypackagerelease.section IS 'The archive section that this binarypackage is in. E.g. devel, libdevel, editors';
+
+
+COMMENT ON COLUMN binarypackagerelease.priority IS 'The priority that this package has. E.g. Base, Standard, Extra, Optional';
+
+
+COMMENT ON COLUMN binarypackagerelease.shlibdeps IS 'The shared library dependencies of this binary package';
+
+
+COMMENT ON COLUMN binarypackagerelease.depends IS 'The list of packages this binarypackage depends on';
+
+
+COMMENT ON COLUMN binarypackagerelease.recommends IS 'The list of packages this binarypackage recommends. Recommended packages often enhance the behaviour of a package.';
+
+
+COMMENT ON COLUMN binarypackagerelease.suggests IS 'The list of packages this binarypackage suggests.';
+
+
+COMMENT ON COLUMN binarypackagerelease.conflicts IS 'The list of packages this binarypackage conflicts with.';
+
+
+COMMENT ON COLUMN binarypackagerelease.replaces IS 'The list of packages this binarypackage replaces files in. Often this is used to provide an upgrade path between two binarypackages of different names';
+
+
+COMMENT ON COLUMN binarypackagerelease.provides IS 'The list of virtual packages (or real packages under some circumstances) which this binarypackage provides.';
+
+
+COMMENT ON COLUMN binarypackagerelease.essential IS 'Whether or not this binarypackage is essential to the smooth operation of a base system';
+
+
+COMMENT ON COLUMN binarypackagerelease.installedsize IS 'What the installed size of the binarypackage is. This is represented as a number of kilobytes of storage.';
+
+
+COMMENT ON COLUMN binarypackagerelease.architecturespecific IS 'This field indicates whether or not a binarypackage is architecture-specific. If it is not specific to any given architecture then it can automatically be included in all the distroarchseries which pertain.';
+
+
+COMMENT ON COLUMN binarypackagerelease.pre_depends IS 'The list of packages this binary requires to be installed beforehand in apt/dpkg format, as it is in control file "Pre-Depends:" field.';
+
+
+COMMENT ON COLUMN binarypackagerelease.enhances IS 'The list of packages pointed as "enhanced" after the installation of this package, as it is in control file "Enhances:" field.';
+
+
+COMMENT ON COLUMN binarypackagerelease.breaks IS 'The list of packages which will be broken by the installtion of this package, as it is in the control file "Breaks:" field.';
+
+
+COMMENT ON COLUMN binarypackagerelease.debug_package IS 'The corresponding binary package release containing debug symbols for this binary, if any.';
+
+
+COMMENT ON COLUMN binarypackagerelease.user_defined_fields IS 'A JSON struct containing a sequence of key-value pairs with user defined fields in the control file.';
+
+
+COMMENT ON COLUMN binarypackagerelease.homepage IS 'Upstream project homepage URL, not checked for validity.';
+
+
 CREATE TABLE component (
     id integer NOT NULL,
     name text NOT NULL,
@@ -428,6 +5396,16 @@
     CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
+
+COMMENT ON TABLE component IS 'Known components in Launchpad';
+
+
+COMMENT ON COLUMN component.name IS 'Component name text';
+
+
+COMMENT ON COLUMN component.description IS 'Description of this component.';
+
+
 CREATE TABLE distroarchseries (
     id integer NOT NULL,
     distroseries integer NOT NULL,
@@ -437,9 +5415,37 @@
     official boolean NOT NULL,
     package_count integer DEFAULT 0 NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    supports_virtualized boolean DEFAULT false NOT NULL
+    supports_virtualized boolean DEFAULT false NOT NULL,
+    enabled boolean DEFAULT true NOT NULL,
+    CONSTRAINT valid_architecturetag CHECK (valid_name(architecturetag))
 );
 
+
+COMMENT ON TABLE distroarchseries IS 'DistroArchSeries: A soyuz distribution release for a given architecture. A distroseries runs on various architectures. The distroarchseries groups that architecture-specific stuff.';
+
+
+COMMENT ON COLUMN distroarchseries.distroseries IS 'The distribution which this distroarchseries is part of.';
+
+
+COMMENT ON COLUMN distroarchseries.processorfamily IS 'A link to the ProcessorFamily table, giving the architecture of this DistroArchSeries.';
+
+
+COMMENT ON COLUMN distroarchseries.architecturetag IS 'The name of this architecture in the context of this specific distro release. For example, some distributions might label amd64 as amd64, others might call is x86_64. This information is used, for example, in determining the names of the actual package files... such as the "amd64" part of "apache2_2.0.56-1_amd64.deb"';
+
+
+COMMENT ON COLUMN distroarchseries.official IS 'Whether or not this architecture or "port" is an official release. If it is not official then you may not be able to install it or get all the packages for it.';
+
+
+COMMENT ON COLUMN distroarchseries.package_count IS 'A cache of the number of binary packages published in this distro arch release. The count only includes packages published in the release pocket.';
+
+
+COMMENT ON COLUMN distroarchseries.supports_virtualized IS 'Whether or not
+virtualized build support should be provided by this specific distroarchseries';
+
+
+COMMENT ON COLUMN distroarchseries.enabled IS 'Whether to allow build creation and publishing for this DistroArchSeries.';
+
+
 CREATE TABLE distroseries (
     id integer NOT NULL,
     distribution integer NOT NULL,
@@ -450,8 +5456,7 @@
     releasestatus integer NOT NULL,
     datereleased timestamp without time zone,
     parent_series integer,
-    owner integer NOT NULL,
-    lucilleconfig text,
+    registrant integer NOT NULL,
     summary text NOT NULL,
     displayname text NOT NULL,
     datelastlangpack timestamp without time zone,
@@ -468,11 +5473,87 @@
     language_pack_delta integer,
     language_pack_proposed integer,
     language_pack_full_export_requested boolean DEFAULT false NOT NULL,
+    backports_not_automatic boolean DEFAULT false NOT NULL,
+    include_long_descriptions boolean DEFAULT true NOT NULL,
     CONSTRAINT valid_language_pack_delta CHECK (((language_pack_base IS NOT NULL) OR (language_pack_delta IS NULL))),
     CONSTRAINT valid_name CHECK (valid_name(name)),
     CONSTRAINT valid_version CHECK (sane_version(version))
 );
 
+
+COMMENT ON TABLE distroseries IS 'DistroSeries: A soyuz distribution release. A DistroSeries is a given version of a distribution. E.g. "Warty" "Hoary" "Sarge" etc.';
+
+
+COMMENT ON COLUMN distroseries.distribution IS 'The distribution which contains this distroseries.';
+
+
+COMMENT ON COLUMN distroseries.name IS 'The unique name of the distroseries. This is a short name in lower case and would be used in sources.list configuration and in generated URLs. E.g. "warty" "sarge" "sid"';
+
+
+COMMENT ON COLUMN distroseries.title IS 'The display-name title of the distroseries E.g. "Warty Warthog"';
+
+
+COMMENT ON COLUMN distroseries.description IS 'The long detailed description of the release. This may describe the focus of the release or other related information.';
+
+
+COMMENT ON COLUMN distroseries.version IS 'The version of the release. E.g. warty would be "4.10" and hoary would be "5.4"';
+
+
+COMMENT ON COLUMN distroseries.releasestatus IS 'The current release status of this distroseries. E.g. "pre-release freeze" or "released"';
+
+
+COMMENT ON COLUMN distroseries.datereleased IS 'The date on which this distroseries was released. (obviously only valid for released distributions)';
+
+
+COMMENT ON COLUMN distroseries.parent_series IS 'The parent distroseries on which this distribution is based. This is related to the inheritance stuff.';
+
+
+COMMENT ON COLUMN distroseries.registrant IS 'The ultimate owner of this distroseries.';
+
+
+COMMENT ON COLUMN distroseries.summary IS 'A brief summary of the distro release. This will be displayed in bold at the top of the distroseries page, above the distroseries description. It should include any high points that are particularly important to draw to the attention of users.';
+
+
+COMMENT ON COLUMN distroseries.datelastlangpack IS 'The date we last generated a base language pack for this release. Language update packs for this release will only include translations added after that date.';
+
+
+COMMENT ON COLUMN distroseries.messagecount IS 'This is a cached value and may be a few hours out of sync with reality. It should, however, be in sync with the values in DistroSeriesLanguage, and should never be updated separately. The total number of translation messages in this distro release, as per IRosettaStats.';
+
+
+COMMENT ON COLUMN distroseries.nominatedarchindep IS 'This is the DistroArchSeries nominated to build architecture independent packages within this DistroRelase, it is mandatory for buildable distroseries, i.e., Auto Build System will avoid to create build jobs for a DistroSeries with no nominatedarchindep, but the database model allow us to do it (for non-buildable DistroSeries). See further info in NominatedArchIndep specification.';
+
+
+COMMENT ON COLUMN distroseries.changeslist IS 'The email address (name name) of the changes announcement list for this distroseries. If NULL, no announcement mail will be sent.';
+
+
+COMMENT ON COLUMN distroseries.binarycount IS 'A cache of the number of distinct binary package names published in this distro release.';
+
+
+COMMENT ON COLUMN distroseries.sourcecount IS 'A cache of the number of distinct source package names published in this distro release.';
+
+
+COMMENT ON COLUMN distroseries.driver IS 'This is a person or team who can act as a driver for this specific release - note that the distribution drivers can also set goals for any release.';
+
+
+COMMENT ON COLUMN distroseries.hide_all_translations IS 'Whether we should hid
+e all available translations for this distro release to non admin users.';
+
+
+COMMENT ON COLUMN distroseries.defer_translation_imports IS 'Don''t accept PO imports for this release just now.';
+
+
+COMMENT ON COLUMN distroseries.language_pack_base IS 'Current full export language pack for this distribution release.';
+
+
+COMMENT ON COLUMN distroseries.language_pack_delta IS 'Current language pack update based on language_pack_base information.';
+
+
+COMMENT ON COLUMN distroseries.language_pack_proposed IS 'Either a full or update language pack being tested to be used in language_pack_base or language_pack_delta.';
+
+
+COMMENT ON COLUMN distroseries.language_pack_full_export_requested IS 'Whether next language pack export should be a full export or an update.';
+
+
 CREATE TABLE libraryfilealias (
     id integer NOT NULL,
     content integer,
@@ -486,10 +5567,38 @@
     CONSTRAINT valid_filename CHECK ((filename !~~ '%/%'::text))
 );
 
+
+COMMENT ON TABLE libraryfilealias IS 'LibraryFileAlias: A librarian file''s alias. The librarian stores, along with the file contents, a record stating the file name and mimetype. This table represents it.';
+
+
+COMMENT ON COLUMN libraryfilealias.content IS 'The libraryfilecontent which is the data in this file.';
+
+
+COMMENT ON COLUMN libraryfilealias.filename IS 'The name of the file. E.g. "foo_1.0-1_i386.deb"';
+
+
+COMMENT ON COLUMN libraryfilealias.mimetype IS 'The mime type of the file. E.g. "application/x-debian-package"';
+
+
+COMMENT ON COLUMN libraryfilealias.expires IS 'The expiry date of this file. If NULL, this item may be removed as soon as it is no longer referenced. If set, the item will not be removed until this date. Once the date is passed, the file may be removed from disk even if this item is still being referenced (in which case content.deleted will be true)';
+
+
+COMMENT ON COLUMN libraryfilealias.last_accessed IS 'Roughly when this file was last retrieved from the Librarian. Initially set to this item''s creation date.';
+
+
+COMMENT ON COLUMN libraryfilealias.date_created IS 'The timestamp when this alias was created.';
+
+
+COMMENT ON COLUMN libraryfilealias.restricted IS 'Is this file available only from the restricted librarian?';
+
+
+COMMENT ON COLUMN libraryfilealias.hits IS 'The number of times this file has been downloaded.';
+
+
 CREATE TABLE sourcepackagerelease (
     id integer NOT NULL,
     creator integer NOT NULL,
-    version text NOT NULL,
+    version debversion NOT NULL,
     dateuploaded timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
     urgency integer NOT NULL,
     dscsigningkey integer,
@@ -514,36 +5623,197 @@
     build_conflicts_indep text,
     sourcepackage_recipe_build integer,
     changelog integer,
-    CONSTRAINT valid_version CHECK (valid_debian_version(version))
+    user_defined_fields text,
+    homepage text,
+    CONSTRAINT valid_version CHECK (valid_debian_version((version)::text))
 );
 
+
+COMMENT ON TABLE sourcepackagerelease IS 'SourcePackageRelease: A source
+package release. This table represents a specific release of a source
+package. Source package releases may be published into a distroseries, or
+even multiple distroseries.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.creator IS 'The creator of this
+sourcepackagerelease. This is the person referred to in the top entry in the
+package changelog in debian terms. Note that a source package maintainer in
+Ubuntu might be person A, but a particular release of that source package
+might in fact have been created by a different person B. The maintainer
+would be recorded in the Maintainership table, while the creator of THIS
+release would be recorded in the SourcePackageRelease.creator field.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.version IS 'The version string for
+this source package release. E.g. "1.0-2" or "1.4-5ubuntu9.1". Note that, in
+ubuntu-style and redhat-style distributions, the version+sourcepackagename
+is unique, even across distroseries. In other words, you cannot have a
+foo-1.2-1 package in Hoary that is different from foo-1.2-1 in Warty.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.dateuploaded IS 'The date/time that
+this sourcepackagerelease was first uploaded to the Launchpad.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.urgency IS 'The urgency of the
+upload. This is generally used to prioritise buildd activity but may also be
+used for "testing" systems or security work in the future. The "urgency" is
+set by the uploader, in the DSC file.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.dscsigningkey IS 'The GPG key used to
+sign the DSC. This is not necessarily the maintainer''s key, or the
+creator''s key. For example, it''s possible to produce a package, then ask a
+sponsor to upload it.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.component IS 'The component in which
+this sourcepackagerelease is intended (by the uploader) to reside. E.g.
+main, universe, restricted. Note that the distribution managers will often
+override this data and publish the package in an entirely different
+component.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.changelog_entry IS 'Changelog text section extracted from the changesfile.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.builddepends IS 'The build
+dependencies for this source package release.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.builddependsindep IS 'The
+architecture-independant build dependancies for this source package release.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.architecturehintlist IS 'The
+architectures which this source package release believes it should be built.
+This is used as a hint to the build management system when deciding what
+builds are still needed.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.dsc IS 'The "Debian Source Control"
+file for the sourcepackagerelease, from its upload into Ubuntu for the
+first time.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.section IS 'This integer field references the Section which the source package claims to be in';
+
+
+COMMENT ON COLUMN sourcepackagerelease.maintainer IS 'Reference to the person noted as source package maintainer in the DSC.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.sourcepackagename IS 'Reference to a SourcePackageName.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.upload_distroseries IS 'The
+distroseries into which this source package release was uploaded into
+Launchpad / Ubuntu for the first time. In general, this will be the
+development Ubuntu release into which this package was uploaded. For a
+package which was unchanged between warty and hoary, this would show Warty.
+For a package which was uploaded into Hoary, this would show Hoary.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.format IS 'The format of this
+sourcepackage release, e.g. DPKG, RPM, EBUILD, etc. This is an enum, and the
+values are listed in dbschema.SourcePackageFormat';
+
+
+COMMENT ON COLUMN sourcepackagerelease.dsc_maintainer_rfc822 IS 'The original maintainer line in RFC-822 format, to be used in archive indexes.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.dsc_standards_version IS 'DSC standards version (such as "3.6.2", "3.5.9", etc) used to build this source.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.dsc_format IS 'DSC format version (such as "1.0").';
+
+
+COMMENT ON COLUMN sourcepackagerelease.dsc_binaries IS 'DSC binary line, claimed binary-names produce by this source.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.upload_archive IS 'The archive into which this sourcepackagerelese was originally uploaded.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.copyright IS 'The copyright associated with this sourcepackage. Often in the case of debian packages and will be found after the installation in /usr/share/doc/<binarypackagename>/copyright';
+
+
+COMMENT ON COLUMN sourcepackagerelease.build_conflicts IS 'The list of packages that will conflict with this source while building, as mentioned in the control file "Build-Conflicts:" field.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.build_conflicts_indep IS 'The list of packages that will conflict with this source while building in architecture independent environment, as mentioned in the control file "Build-Conflicts-Indep:" field.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.changelog IS 'The LibraryFileAlias ID of changelog associated with this sourcepackage.  Often in the case of debian packages and will be found after the installation in /usr/share/doc/<binarypackagename>/changelog.Debian.gz';
+
+
+COMMENT ON COLUMN sourcepackagerelease.user_defined_fields IS 'A JSON struct containing a sequence of key-value pairs with user defined fields in the control file.';
+
+
+COMMENT ON COLUMN sourcepackagerelease.homepage IS 'Upstream project homepage URL, not checked for validity.';
+
+
 CREATE VIEW binarypackagefilepublishing AS
     SELECT (((libraryfilealias.id)::text || '.'::text) || (securebinarypackagepublishinghistory.id)::text) AS id, distroseries.distribution, securebinarypackagepublishinghistory.id AS binarypackagepublishing, component.name AS componentname, libraryfilealias.filename AS libraryfilealiasfilename, sourcepackagename.name AS sourcepackagename, binarypackagefile.libraryfile AS libraryfilealias, distroseries.name AS distroseriesname, distroarchseries.architecturetag, securebinarypackagepublishinghistory.status AS publishingstatus, securebinarypackagepublishinghistory.pocket, securebinarypackagepublishinghistory.archive FROM (((((((((binarypackagepublishinghistory securebinarypackagepublishinghistory JOIN binarypackagerelease ON ((securebinarypackagepublishinghistory.binarypackagerelease = binarypackagerelease.id))) JOIN binarypackagebuild ON ((binarypackagerelease.build = binarypackagebuild.id))) JOIN sourcepackagerelease ON ((binarypackagebuild.source_package_release = sourcepackagerelease.id))) JOIN sourcepackagename ON ((sourcepackagerelease.sourcepackagename = sourcepackagename.id))) JOIN binarypackagefile ON ((binarypackagefile.binarypackagerelease = binarypackagerelease.id))) JOIN libraryfilealias ON ((binarypackagefile.libraryfile = libraryfilealias.id))) JOIN distroarchseries ON ((securebinarypackagepublishinghistory.distroarchseries = distroarchseries.id))) JOIN distroseries ON ((distroarchseries.distroseries = distroseries.id))) JOIN component ON ((securebinarypackagepublishinghistory.component = component.id))) WHERE (securebinarypackagepublishinghistory.dateremoved IS NULL);
 
+
+COMMENT ON VIEW binarypackagefilepublishing IS 'This view is used mostly by Lucille while performing publishing and unpublishing operations. It lists all the files associated with a binarypackage and collates all the textual representations needed for publishing components etc to allow rapid queries from SQLObject.';
+
+
 CREATE SEQUENCE binarypackagename_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE binarypackagename_id_seq OWNED BY binarypackagename.id;
 
+
+CREATE TABLE binarypackagepath (
+    id integer NOT NULL,
+    path bytea NOT NULL
+);
+
+
+CREATE SEQUENCE binarypackagepath_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE binarypackagepath_id_seq OWNED BY binarypackagepath.id;
+
+
 CREATE SEQUENCE binarypackagepublishinghistory_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE binarypackagepublishinghistory_id_seq OWNED BY binarypackagepublishinghistory.id;
 
+
 CREATE SEQUENCE binarypackagerelease_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE binarypackagerelease_id_seq OWNED BY binarypackagerelease.id;
 
+
+CREATE TABLE binarypackagereleasecontents (
+    binarypackagerelease integer NOT NULL,
+    binarypackagepath integer NOT NULL
+);
+
+
 CREATE TABLE binarypackagereleasedownloadcount (
     id integer NOT NULL,
     archive integer NOT NULL,
@@ -553,6 +5823,7 @@
     count integer NOT NULL
 );
 
+
 CREATE SEQUENCE binarypackagereleasedownloadcount_id_seq
     START WITH 1
     INCREMENT BY 1
@@ -560,60 +5831,9 @@
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE binarypackagereleasedownloadcount_id_seq OWNED BY binarypackagereleasedownloadcount.id;
 
-CREATE TABLE bounty (
-    id integer NOT NULL,
-    name text NOT NULL,
-    title text NOT NULL,
-    summary text NOT NULL,
-    description text NOT NULL,
-    usdvalue numeric(10,2) NOT NULL,
-    difficulty integer NOT NULL,
-    reviewer integer NOT NULL,
-    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone),
-    owner integer NOT NULL,
-    deadline timestamp without time zone,
-    claimant integer,
-    dateclaimed timestamp without time zone,
-    bountystatus integer DEFAULT 1 NOT NULL
-);
-
-CREATE SEQUENCE bounty_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE bounty_id_seq OWNED BY bounty.id;
-
-CREATE TABLE bountymessage (
-    id integer NOT NULL,
-    bounty integer NOT NULL,
-    message integer NOT NULL
-);
-
-CREATE SEQUENCE bountymessage_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE bountymessage_id_seq OWNED BY bountymessage.id;
-
-CREATE TABLE bountysubscription (
-    id integer NOT NULL,
-    bounty integer NOT NULL,
-    person integer NOT NULL
-);
-
-CREATE SEQUENCE bountysubscription_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE bountysubscription_id_seq OWNED BY bountysubscription.id;
 
 CREATE TABLE branch (
     id integer NOT NULL,
@@ -640,8 +5860,6 @@
     private boolean DEFAULT false NOT NULL,
     branch_type integer NOT NULL,
     reviewer integer,
-    merge_robot integer,
-    merge_control_status integer DEFAULT 1 NOT NULL,
     date_last_modified timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     registrant integer NOT NULL,
     branch_format integer,
@@ -654,7 +5872,10 @@
     target_suffix text,
     unique_name text,
     size_on_disk bigint,
-    CONSTRAINT branch_merge_control CHECK (((merge_robot IS NULL) OR (merge_control_status = ANY (ARRAY[3, 4])))),
+    merge_queue integer,
+    merge_queue_config text,
+    transitively_private boolean DEFAULT true NOT NULL,
+    access_policy integer,
     CONSTRAINT branch_type_url_consistent CHECK (((((branch_type = 2) AND (url IS NOT NULL)) OR ((branch_type = ANY (ARRAY[1, 3])) AND (url IS NULL))) OR (branch_type = 4))),
     CONSTRAINT branch_url_no_trailing_slash CHECK ((url !~~ '%/'::text)),
     CONSTRAINT branch_url_not_supermirror CHECK ((url !~~ 'http://bazaar.launchpad.net/%'::text)),
@@ -664,14 +5885,96 @@
     CONSTRAINT valid_url CHECK (valid_absolute_url(url))
 );
 
+
+COMMENT ON TABLE branch IS 'Bzr branch';
+
+
+COMMENT ON COLUMN branch.summary IS 'A single paragraph description of the branch';
+
+
+COMMENT ON COLUMN branch.home_page IS 'This column is deprecated and to be removed soon.';
+
+
+COMMENT ON COLUMN branch.whiteboard IS 'Notes on the current status of the branch';
+
+
+COMMENT ON COLUMN branch.lifecycle_status IS 'Authors assesment of the branchs maturity';
+
+
+COMMENT ON COLUMN branch.last_mirrored IS 'The time when the branch was last mirrored.';
+
+
+COMMENT ON COLUMN branch.mirror_status_message IS 'The last message we got when mirroring this branch.';
+
+
+COMMENT ON COLUMN branch.last_scanned IS 'The time when the branch was last scanned.';
+
+
+COMMENT ON COLUMN branch.last_scanned_id IS 'The revision ID of the branch when it was last scanned.';
+
+
+COMMENT ON COLUMN branch.last_mirrored_id IS 'The revision ID of the branch when it was last mirrored.';
+
+
+COMMENT ON COLUMN branch.revision_count IS 'The number of revisions in the associated bazaar branch revision_history.';
+
+
+COMMENT ON COLUMN branch.next_mirror_time IS 'The time when we will next mirror this branch (NULL means never). This will be set automatically by pushing to a hosted branch, which, once mirrored, will be set back to NULL.';
+
+
+COMMENT ON COLUMN branch.private IS 'If the branch is private, then only the owner and subscribers of the branch can see it.';
+
+
+COMMENT ON COLUMN branch.branch_type IS 'Branches are currently one of HOSTED (1), MIRRORED (2), or IMPORTED (3).';
+
+
+COMMENT ON COLUMN branch.reviewer IS 'The reviewer (person or) team are able to transition merge proposals targetted at the branch throught the CODE_APPROVED state.';
+
+
+COMMENT ON COLUMN branch.date_last_modified IS 'A branch is modified any time a user updates something using a view, a new revision for the branch is scanned, or the branch is linked to a bug, blueprint or merge proposal.';
+
+
+COMMENT ON COLUMN branch.registrant IS 'The user that registered the branch.';
+
+
+COMMENT ON COLUMN branch.branch_format IS 'The bzr branch format';
+
+
+COMMENT ON COLUMN branch.repository_format IS 'The bzr repository format';
+
+
+COMMENT ON COLUMN branch.metadir_format IS 'The bzr metadir format';
+
+
+COMMENT ON COLUMN branch.stacked_on IS 'The Launchpad branch that this branch is stacked on (if any).';
+
+
+COMMENT ON COLUMN branch.distroseries IS 'The distribution series that the branch belongs to.';
+
+
+COMMENT ON COLUMN branch.sourcepackagename IS 'The source package this is a branch of.';
+
+
+COMMENT ON COLUMN branch.size_on_disk IS 'The size in bytes of this branch in the mirrored area.';
+
+
+COMMENT ON COLUMN branch.merge_queue IS 'A reference to the BranchMergeQueue record that manages merges.';
+
+
+COMMENT ON COLUMN branch.merge_queue_config IS 'A JSON string of configuration values that can be read by a merge queue script.';
+
+
 CREATE SEQUENCE branch_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE branch_id_seq OWNED BY branch.id;
 
+
 CREATE TABLE branchjob (
     id integer NOT NULL,
     job integer NOT NULL,
@@ -680,14 +5983,33 @@
     json_data text
 );
 
+
+COMMENT ON TABLE branchjob IS 'Contains references to jobs that are executed for a branch.';
+
+
+COMMENT ON COLUMN branchjob.job IS 'A reference to a row in the Job table that has all the common job details.';
+
+
+COMMENT ON COLUMN branchjob.branch IS 'The branch that this job is for.';
+
+
+COMMENT ON COLUMN branchjob.job_type IS 'The type of job, like new revisions, or attribute change.';
+
+
+COMMENT ON COLUMN branchjob.json_data IS 'Data that is specific to the type of job, whether this be the revisions to send email out for, or the changes that were recorded for the branch.';
+
+
 CREATE SEQUENCE branchjob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE branchjob_id_seq OWNED BY branchjob.id;
 
+
 CREATE TABLE branchmergeproposal (
     id integer NOT NULL,
     registrant integer NOT NULL,
@@ -716,21 +6038,108 @@
     merge_log_file integer,
     superseded_by integer,
     root_message_id text,
-    review_diff integer,
     merge_diff integer,
     description text,
     CONSTRAINT different_branches CHECK ((((source_branch <> target_branch) AND (dependent_branch <> source_branch)) AND (dependent_branch <> target_branch))),
     CONSTRAINT positive_revno CHECK (((merged_revno IS NULL) OR (merged_revno > 0)))
 );
 
+
+COMMENT ON TABLE branchmergeproposal IS 'Branch merge proposals record the intent of landing (or merging) one branch on another.';
+
+
+COMMENT ON COLUMN branchmergeproposal.registrant IS 'The person that created the merge proposal.';
+
+
+COMMENT ON COLUMN branchmergeproposal.source_branch IS 'The branch where the work is being written.  This branch contains the changes that the registrant wants to land.';
+
+
+COMMENT ON COLUMN branchmergeproposal.target_branch IS 'The branch where the user wants the changes from the source branch to be merged into.';
+
+
+COMMENT ON COLUMN branchmergeproposal.dependent_branch IS 'If the source branch was not branched off the target branch, then this is considered the dependent_branch.';
+
+
+COMMENT ON COLUMN branchmergeproposal.whiteboard IS 'Used to write other information about the branch, like test URLs.';
+
+
+COMMENT ON COLUMN branchmergeproposal.date_merged IS 'This is the date that merge occurred.';
+
+
+COMMENT ON COLUMN branchmergeproposal.merged_revno IS 'This is the revision number of the revision on the target branch that includes the merge from the source branch.';
+
+
+COMMENT ON COLUMN branchmergeproposal.merge_reporter IS 'This is the user that marked the proposal as merged.';
+
+
+COMMENT ON COLUMN branchmergeproposal.date_created IS 'When the registrant created the merge proposal.';
+
+
+COMMENT ON COLUMN branchmergeproposal.commit_message IS 'This is the commit message that is to be used when the branch is landed by a robot.';
+
+
+COMMENT ON COLUMN branchmergeproposal.queue_position IS 'The position on the merge proposal in the overall landing queue.  If the branch has a merge_robot set and the merge robot controls multiple branches then the queue position is unique over all the queued merge proposals for the landing robot.';
+
+
+COMMENT ON COLUMN branchmergeproposal.queue_status IS 'This is the current state of the merge proposal.';
+
+
+COMMENT ON COLUMN branchmergeproposal.date_review_requested IS 'The date that the merge proposal enters the REVIEW_REQUESTED state. This is stored so that we can determine how long a branch has been waiting for code approval.';
+
+
+COMMENT ON COLUMN branchmergeproposal.reviewer IS 'The individual who said that the code in this branch is OK to land.';
+
+
+COMMENT ON COLUMN branchmergeproposal.date_reviewed IS 'When the reviewer said the code is OK to land.';
+
+
+COMMENT ON COLUMN branchmergeproposal.reviewed_revision_id IS 'The Bazaar revision ID that was approved to land.';
+
+
+COMMENT ON COLUMN branchmergeproposal.queuer IS 'The individual who submitted the branch to the merge queue. This is usually the merge proposal registrant.';
+
+
+COMMENT ON COLUMN branchmergeproposal.date_queued IS 'When the queuer submitted the branch to the merge queue.';
+
+
+COMMENT ON COLUMN branchmergeproposal.queued_revision_id IS 'The Bazaar revision ID that is queued to land.';
+
+
+COMMENT ON COLUMN branchmergeproposal.merger IS 'The merger is the person who merged the branch.';
+
+
+COMMENT ON COLUMN branchmergeproposal.merged_revision_id IS 'The Bazaar revision ID that was actually merged.  If the owner of the source branch is a trusted person, this may be different than the revision_id that was actually queued or reviewed.';
+
+
+COMMENT ON COLUMN branchmergeproposal.date_merge_started IS 'If the merge is performed by a bot the time the merge was started is recorded otherwise it is NULL.';
+
+
+COMMENT ON COLUMN branchmergeproposal.date_merge_finished IS 'If the merge is performed by a bot the time the merge was finished is recorded otherwise it is NULL.';
+
+
+COMMENT ON COLUMN branchmergeproposal.merge_log_file IS 'If the merge is performed by a bot the log file is accessible from the librarian.';
+
+
+COMMENT ON COLUMN branchmergeproposal.superseded_by IS 'The proposal to merge has been superceded by this one.';
+
+
+COMMENT ON COLUMN branchmergeproposal.root_message_id IS 'The root message of this BranchMergeProposal''s mail thread.';
+
+
+COMMENT ON COLUMN branchmergeproposal.merge_diff IS 'The diff showing the predicted result of a merge.';
+
+
 CREATE SEQUENCE branchmergeproposal_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE branchmergeproposal_id_seq OWNED BY branchmergeproposal.id;
 
+
 CREATE TABLE branchmergeproposaljob (
     id integer NOT NULL,
     job integer NOT NULL,
@@ -739,47 +6148,89 @@
     json_data text
 );
 
+
+COMMENT ON TABLE branchmergeproposaljob IS 'Contains references to jobs that are executed for a branch merge proposal.';
+
+
+COMMENT ON COLUMN branchmergeproposaljob.job IS 'A reference to a row in the Job table that has all the common job details.';
+
+
+COMMENT ON COLUMN branchmergeproposaljob.branch_merge_proposal IS 'The branch merge proposal that this job is for.';
+
+
+COMMENT ON COLUMN branchmergeproposaljob.job_type IS 'The type of job, like new proposal, review comment, or new review requested.';
+
+
+COMMENT ON COLUMN branchmergeproposaljob.json_data IS 'Data that is specific to the type of job, normally references to code review messages and or votes.';
+
+
 CREATE SEQUENCE branchmergeproposaljob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE branchmergeproposaljob_id_seq OWNED BY branchmergeproposaljob.id;
 
-CREATE TABLE branchmergerobot (
+
+CREATE TABLE branchmergequeue (
     id integer NOT NULL,
     registrant integer NOT NULL,
     owner integer NOT NULL,
     name text NOT NULL,
-    whiteboard text,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
+    description text,
+    configuration text,
+    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
+    CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
-CREATE SEQUENCE branchmergerobot_id_seq
+
+COMMENT ON TABLE branchmergequeue IS 'Queue for managing the merge workflow for branches.';
+
+
+COMMENT ON COLUMN branchmergequeue.id IS 'The id of the merge queue.';
+
+
+COMMENT ON COLUMN branchmergequeue.registrant IS 'A reference to the person who created the merge queue.';
+
+
+COMMENT ON COLUMN branchmergequeue.owner IS 'A reference to the person who owns the merge queue.';
+
+
+COMMENT ON COLUMN branchmergequeue.name IS 'The name of the queue.';
+
+
+COMMENT ON COLUMN branchmergequeue.description IS 'A description of the queue.';
+
+
+COMMENT ON COLUMN branchmergequeue.configuration IS 'A JSON string of configuration data to be read by the merging script.';
+
+
+COMMENT ON COLUMN branchmergequeue.date_created IS 'The date the queue was created.';
+
+
+CREATE SEQUENCE branchmergequeue_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
-ALTER SEQUENCE branchmergerobot_id_seq OWNED BY branchmergerobot.id;
+
+ALTER SEQUENCE branchmergequeue_id_seq OWNED BY branchmergequeue.id;
+
 
 CREATE TABLE branchrevision (
-    id integer NOT NULL,
     sequence integer,
     branch integer NOT NULL,
     revision integer NOT NULL
-);
+)
+WITH (fillfactor=100);
 ALTER TABLE ONLY branchrevision ALTER COLUMN branch SET STATISTICS 500;
 ALTER TABLE ONLY branchrevision ALTER COLUMN revision SET STATISTICS 500;
 
-CREATE SEQUENCE branchrevision_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE branchrevision_id_seq OWNED BY branchrevision.id;
 
 CREATE TABLE branchsubscription (
     id integer NOT NULL,
@@ -792,14 +6243,36 @@
     subscribed_by integer NOT NULL
 );
 
+
+COMMENT ON TABLE branchsubscription IS 'An association between a person or team and a bazaar branch.';
+
+
+COMMENT ON COLUMN branchsubscription.person IS 'The person or team associated with the branch.';
+
+
+COMMENT ON COLUMN branchsubscription.branch IS 'The branch associated with the person or team.';
+
+
+COMMENT ON COLUMN branchsubscription.notification_level IS 'The level of email the person wants to receive from branch updates.';
+
+
+COMMENT ON COLUMN branchsubscription.max_diff_lines IS 'If the generated diff for a revision is larger than this number, then the diff is not sent in the notification email.';
+
+
+COMMENT ON COLUMN branchsubscription.review_level IS 'The level of email the person wants to receive from review activity';
+
+
 CREATE SEQUENCE branchsubscription_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE branchsubscription_id_seq OWNED BY branchsubscription.id;
 
+
 CREATE TABLE branchvisibilitypolicy (
     id integer NOT NULL,
     project integer,
@@ -809,150 +6282,44 @@
     CONSTRAINT only_one_target CHECK (((project IS NULL) <> (product IS NULL)))
 );
 
+
+COMMENT ON TABLE branchvisibilitypolicy IS 'Defines the policy for the initial visibility of branches.';
+
+
+COMMENT ON COLUMN branchvisibilitypolicy.project IS 'Even though projects don''t directly have branches themselves, if a product of the project does not specify its own branch visibility policies, those of the project are used.';
+
+
+COMMENT ON COLUMN branchvisibilitypolicy.product IS 'The product that the visibility policies apply to.';
+
+
+COMMENT ON COLUMN branchvisibilitypolicy.team IS 'Refers to the team that the policy applies to.  NULL is used to indicate ALL people, as there is no team defined for *everybody*.';
+
+
+COMMENT ON COLUMN branchvisibilitypolicy.policy IS 'An enumerated type, one of PUBLIC or PRIVATE.  PUBLIC is the default value.';
+
+
 CREATE SEQUENCE branchvisibilitypolicy_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE branchvisibilitypolicy_id_seq OWNED BY branchvisibilitypolicy.id;
 
-CREATE TABLE person (
-    id integer NOT NULL,
-    displayname text NOT NULL,
-    teamowner integer,
-    teamdescription text,
-    name text NOT NULL,
-    language integer,
-    fti ts2.tsvector,
-    defaultmembershipperiod integer,
-    defaultrenewalperiod integer,
-    subscriptionpolicy integer DEFAULT 1 NOT NULL,
-    merged integer,
-    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
-    homepage_content text,
-    icon integer,
-    mugshot integer,
-    hide_email_addresses boolean DEFAULT false NOT NULL,
-    creation_rationale integer,
-    creation_comment text,
-    registrant integer,
-    logo integer,
-    renewal_policy integer DEFAULT 10 NOT NULL,
-    personal_standing integer DEFAULT 0 NOT NULL,
-    personal_standing_reason text,
-    mail_resumption_date date,
-    mailing_list_auto_subscribe_policy integer DEFAULT 1 NOT NULL,
-    mailing_list_receive_duplicates boolean DEFAULT true NOT NULL,
-    visibility integer DEFAULT 1 NOT NULL,
-    verbose_bugnotifications boolean DEFAULT false NOT NULL,
-    account integer,
-    CONSTRAINT creation_rationale_not_null_for_people CHECK (((creation_rationale IS NULL) = (teamowner IS NOT NULL))),
-    CONSTRAINT no_loops CHECK ((id <> teamowner)),
-    CONSTRAINT non_empty_displayname CHECK ((btrim(displayname) <> ''::text)),
-    CONSTRAINT people_have_no_emblems CHECK (((icon IS NULL) OR (teamowner IS NOT NULL))),
-    CONSTRAINT sane_defaultrenewalperiod CHECK (CASE WHEN (teamowner IS NULL) THEN (defaultrenewalperiod IS NULL) WHEN (renewal_policy = ANY (ARRAY[20, 30])) THEN ((defaultrenewalperiod IS NOT NULL) AND (defaultrenewalperiod > 0)) ELSE ((defaultrenewalperiod IS NULL) OR (defaultrenewalperiod > 0)) END),
-    CONSTRAINT teams_have_no_account CHECK (((account IS NULL) OR (teamowner IS NULL))),
-    CONSTRAINT valid_name CHECK (valid_name(name))
-);
-
-CREATE TABLE product (
-    id integer NOT NULL,
-    project integer,
-    owner integer NOT NULL,
-    name text NOT NULL,
-    displayname text NOT NULL,
-    title text NOT NULL,
-    summary text NOT NULL,
-    description text,
-    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
-    homepageurl text,
-    screenshotsurl text,
-    wikiurl text,
-    listurl text,
-    programminglang text,
-    downloadurl text,
-    lastdoap text,
-    sourceforgeproject text,
-    freshmeatproject text,
-    reviewed boolean DEFAULT false NOT NULL,
-    active boolean DEFAULT true NOT NULL,
-    fti ts2.tsvector,
-    autoupdate boolean DEFAULT false NOT NULL,
-    translationgroup integer,
-    translationpermission integer DEFAULT 1 NOT NULL,
-    official_rosetta boolean DEFAULT false NOT NULL,
-    official_malone boolean DEFAULT false NOT NULL,
-    bug_supervisor integer,
-    security_contact integer,
-    driver integer,
-    bugtracker integer,
-    development_focus integer,
-    homepage_content text,
-    icon integer,
-    mugshot integer,
-    logo integer,
-    official_answers boolean DEFAULT false NOT NULL,
-    private_bugs boolean DEFAULT false NOT NULL,
-    private_specs boolean DEFAULT false NOT NULL,
-    license_info text,
-    official_blueprints boolean DEFAULT false NOT NULL,
-    enable_bug_expiration boolean DEFAULT false NOT NULL,
-    bug_reporting_guidelines text,
-    reviewer_whiteboard text,
-    license_approved boolean DEFAULT false NOT NULL,
-    registrant integer NOT NULL,
-    remote_product text,
-    translation_focus integer,
-    max_bug_heat integer,
-    date_next_suggest_packaging timestamp without time zone,
-    bug_reported_acknowledgement text,
-    answers_usage integer DEFAULT 10 NOT NULL,
-    blueprints_usage integer DEFAULT 10 NOT NULL,
-    translations_usage integer DEFAULT 10 NOT NULL,
-    CONSTRAINT only_launchpad_has_expiration CHECK (((enable_bug_expiration IS FALSE) OR (official_malone IS TRUE))),
-    CONSTRAINT private_bugs_need_contact CHECK (((private_bugs IS FALSE) OR (bug_supervisor IS NOT NULL))),
-    CONSTRAINT valid_name CHECK (valid_name(name))
-);
-
-CREATE VIEW branchwithsortkeys AS
-    SELECT branch.id, branch.title, branch.summary, branch.owner, branch.product, branch.author, branch.name, branch.home_page, branch.url, branch.whiteboard, branch.lifecycle_status, branch.last_mirrored, branch.last_mirror_attempt, branch.mirror_failures, branch.mirror_status_message, branch.last_scanned, branch.last_scanned_id, branch.last_mirrored_id, branch.date_created, branch.revision_count, branch.next_mirror_time, branch.private, branch.branch_type, branch.reviewer, branch.merge_robot, branch.merge_control_status, branch.date_last_modified, branch.registrant, branch.branch_format, branch.repository_format, branch.metadir_format, branch.stacked_on, product.name AS product_name, author.displayname AS author_name, owner.displayname AS owner_name FROM (((branch JOIN person owner ON ((branch.owner = owner.id))) LEFT JOIN product ON ((branch.product = product.id))) LEFT JOIN person author ON ((branch.author = author.id)));
-
-CREATE TABLE bug (
-    id integer NOT NULL,
-    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
-    name text,
-    title text NOT NULL,
-    description text NOT NULL,
-    owner integer NOT NULL,
-    duplicateof integer,
-    fti ts2.tsvector,
-    private boolean DEFAULT false NOT NULL,
-    security_related boolean DEFAULT false NOT NULL,
-    date_last_updated timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    date_made_private timestamp without time zone,
-    who_made_private integer,
-    date_last_message timestamp without time zone,
-    number_of_duplicates integer DEFAULT 0 NOT NULL,
-    message_count integer DEFAULT 0 NOT NULL,
-    users_affected_count integer DEFAULT 0,
-    users_unaffected_count integer DEFAULT 0,
-    heat integer DEFAULT 0 NOT NULL,
-    heat_last_updated timestamp without time zone,
-    latest_patch_uploaded timestamp without time zone,
-    CONSTRAINT notduplicateofself CHECK ((NOT (id = duplicateof))),
-    CONSTRAINT sane_description CHECK (((ltrim(description) <> ''::text) AND (char_length(description) <= 50000))),
-    CONSTRAINT valid_bug_name CHECK (valid_bug_name(name))
-);
 
 CREATE SEQUENCE bug_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bug_id_seq OWNED BY bug.id;
 
+
 CREATE TABLE bugactivity (
     id integer NOT NULL,
     bug integer NOT NULL,
@@ -962,16 +6329,21 @@
     oldvalue text,
     newvalue text,
     message text
-);
+)
+WITH (fillfactor=100);
+
 
 CREATE SEQUENCE bugactivity_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugactivity_id_seq OWNED BY bugactivity.id;
 
+
 CREATE TABLE bugaffectsperson (
     id integer NOT NULL,
     bug integer NOT NULL,
@@ -979,14 +6351,27 @@
     affected boolean DEFAULT true NOT NULL
 );
 
+
+COMMENT ON TABLE bugaffectsperson IS 'This table maintains a mapping between bugs and users indicating that they are affected by that bug. The value is calculated and cached in the Bug.users_affected_count column.';
+
+
+COMMENT ON COLUMN bugaffectsperson.bug IS 'The bug affecting this person.';
+
+
+COMMENT ON COLUMN bugaffectsperson.person IS 'The person affected by this bug.';
+
+
 CREATE SEQUENCE bugaffectsperson_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugaffectsperson_id_seq OWNED BY bugaffectsperson.id;
 
+
 CREATE TABLE bugattachment (
     id integer NOT NULL,
     message integer NOT NULL,
@@ -998,14 +6383,18 @@
     CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
+
 CREATE SEQUENCE bugattachment_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugattachment_id_seq OWNED BY bugattachment.id;
 
+
 CREATE TABLE bugbranch (
     id integer NOT NULL,
     datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
@@ -1016,14 +6405,36 @@
     registrant integer NOT NULL
 );
 
+
+COMMENT ON TABLE bugbranch IS 'A branch related to a bug, most likely a branch for fixing the bug.';
+
+
+COMMENT ON COLUMN bugbranch.bug IS 'The bug associated with this branch.';
+
+
+COMMENT ON COLUMN bugbranch.branch IS 'The branch associated to the bug.';
+
+
+COMMENT ON COLUMN bugbranch.revision_hint IS 'An optional revision at which this branch became interesting to this bug, and/or may contain a fix for the bug.';
+
+
+COMMENT ON COLUMN bugbranch.whiteboard IS 'Additional information about the status of the bugfix in this branch.';
+
+
+COMMENT ON COLUMN bugbranch.registrant IS 'The person who linked the bug to the branch.';
+
+
 CREATE SEQUENCE bugbranch_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugbranch_id_seq OWNED BY bugbranch.id;
 
+
 CREATE TABLE bugcve (
     id integer NOT NULL,
     bug integer NOT NULL,
@@ -1031,14 +6442,21 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE bugcve IS 'A table that records the link between a given malone bug number, and a CVE entry.';
+
+
 CREATE SEQUENCE bugcve_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugcve_id_seq OWNED BY bugcve.id;
 
+
 CREATE TABLE bugjob (
     id integer NOT NULL,
     job integer NOT NULL,
@@ -1047,32 +6465,72 @@
     json_data text
 );
 
+
+COMMENT ON TABLE bugjob IS 'Contains references to jobs to be run against Bugs.';
+
+
+COMMENT ON COLUMN bugjob.bug IS 'The bug on which the job is to be run.';
+
+
+COMMENT ON COLUMN bugjob.job_type IS 'The type of job (enumeration value). Allows us to query the database for a given subset of BugJobs.';
+
+
+COMMENT ON COLUMN bugjob.json_data IS 'A JSON struct containing data for the job.';
+
+
 CREATE SEQUENCE bugjob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugjob_id_seq OWNED BY bugjob.id;
 
+
 CREATE TABLE bugmessage (
     id integer NOT NULL,
     bug integer NOT NULL,
     message integer NOT NULL,
     bugwatch integer,
     remote_comment_id text,
-    visible boolean DEFAULT true NOT NULL,
+    index integer NOT NULL,
+    owner integer NOT NULL,
     CONSTRAINT imported_comment CHECK (((remote_comment_id IS NULL) OR (bugwatch IS NOT NULL)))
 );
 
+
+COMMENT ON TABLE bugmessage IS 'This table maps a message to a bug. In other words, it shows that a particular message is associated with a particular bug.';
+
+
+COMMENT ON COLUMN bugmessage.bugwatch IS 'The external bug this bug comment was imported from.';
+
+
+COMMENT ON COLUMN bugmessage.remote_comment_id IS 'The id this bug comment has in the external bug tracker, if it is an imported comment. If it is NULL while having a bugwatch set, this comment was added in Launchpad and needs to be pushed to the external bug tracker.';
+
+
+COMMENT ON COLUMN bugmessage.index IS 'The index (used in urls) of the message in a particular bug.';
+
+
 CREATE SEQUENCE bugmessage_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugmessage_id_seq OWNED BY bugmessage.id;
 
+
+CREATE TABLE bugmute (
+    person integer NOT NULL,
+    bug integer NOT NULL,
+    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
+);
+
+
 CREATE TABLE bugnomination (
     id integer NOT NULL,
     bug integer NOT NULL,
@@ -1086,30 +6544,82 @@
     CONSTRAINT distroseries_or_productseries CHECK (((distroseries IS NULL) <> (productseries IS NULL)))
 );
 
+
+COMMENT ON TABLE bugnomination IS 'A bug nominated for fixing in a distroseries or productseries';
+
+
+COMMENT ON COLUMN bugnomination.bug IS 'The bug being nominated.';
+
+
+COMMENT ON COLUMN bugnomination.distroseries IS 'The distroseries for which the bug is nominated.';
+
+
+COMMENT ON COLUMN bugnomination.productseries IS 'The productseries for which the bug is nominated.';
+
+
+COMMENT ON COLUMN bugnomination.status IS 'The status of the nomination.';
+
+
+COMMENT ON COLUMN bugnomination.date_created IS 'The date the nomination was submitted.';
+
+
+COMMENT ON COLUMN bugnomination.date_decided IS 'The date the nomination was approved or declined.';
+
+
+COMMENT ON COLUMN bugnomination.owner IS 'The person that submitted the nomination';
+
+
+COMMENT ON COLUMN bugnomination.decider IS 'The person who approved or declined the nomination';
+
+
 CREATE SEQUENCE bugnomination_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugnomination_id_seq OWNED BY bugnomination.id;
 
+
 CREATE TABLE bugnotification (
     id integer NOT NULL,
     bug integer NOT NULL,
     message integer NOT NULL,
     is_comment boolean NOT NULL,
-    date_emailed timestamp without time zone
+    date_emailed timestamp without time zone,
+    status integer DEFAULT 10 NOT NULL,
+    activity integer
 );
 
+
+COMMENT ON TABLE bugnotification IS 'The text representation of changes to a bug, which are used to send email notifications to bug changes.';
+
+
+COMMENT ON COLUMN bugnotification.bug IS 'The bug that was changed.';
+
+
+COMMENT ON COLUMN bugnotification.message IS 'The message the contains the textual representation of the change.';
+
+
+COMMENT ON COLUMN bugnotification.is_comment IS 'Is the change a comment addition.';
+
+
+COMMENT ON COLUMN bugnotification.date_emailed IS 'When this notification was emailed to the bug subscribers.';
+
+
 CREATE SEQUENCE bugnotification_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugnotification_id_seq OWNED BY bugnotification.id;
 
+
 CREATE TABLE bugnotificationarchive (
     id integer NOT NULL,
     bug integer,
@@ -1118,20 +6628,40 @@
     date_emailed timestamp without time zone
 );
 
+
 CREATE TABLE bugnotificationattachment (
     id integer NOT NULL,
     message integer NOT NULL,
     bug_notification integer NOT NULL
 );
 
+
+COMMENT ON TABLE bugnotificationattachment IS 'Attachments to be attached to a bug notification.';
+
+
+COMMENT ON COLUMN bugnotificationattachment.message IS 'A message to be attached to the sent bug notification. It will be attached as a mime/multipart part, with a content type of message/rfc822.';
+
+
+COMMENT ON COLUMN bugnotificationattachment.bug_notification IS 'The bug notification, to which things should be attached to.';
+
+
 CREATE SEQUENCE bugnotificationattachment_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugnotificationattachment_id_seq OWNED BY bugnotificationattachment.id;
 
+
+CREATE TABLE bugnotificationfilter (
+    bug_notification integer NOT NULL,
+    bug_subscription_filter integer NOT NULL
+);
+
+
 CREATE TABLE bugnotificationrecipient (
     id integer NOT NULL,
     bug_notification integer NOT NULL,
@@ -1140,14 +6670,33 @@
     reason_body text NOT NULL
 );
 
+
+COMMENT ON TABLE bugnotificationrecipient IS 'The recipient for a bug notification.';
+
+
+COMMENT ON COLUMN bugnotificationrecipient.bug_notification IS 'The notification this recipient should get.';
+
+
+COMMENT ON COLUMN bugnotificationrecipient.person IS 'The person who should receive this notification.';
+
+
+COMMENT ON COLUMN bugnotificationrecipient.reason_header IS 'The reason this person is receiving this notification (the value for the X-Launchpad-Message-Rationale header).';
+
+
+COMMENT ON COLUMN bugnotificationrecipient.reason_body IS 'A line of text describing the reason this person is receiving this notification (to be included in the email message).';
+
+
 CREATE SEQUENCE bugnotificationrecipient_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugnotificationrecipient_id_seq OWNED BY bugnotificationrecipient.id;
 
+
 CREATE TABLE bugnotificationrecipientarchive (
     id integer NOT NULL,
     bug_notification integer,
@@ -1156,125 +6705,216 @@
     reason_body text
 );
 
-CREATE TABLE bugpackageinfestation (
-    id integer NOT NULL,
-    bug integer NOT NULL,
-    sourcepackagerelease integer NOT NULL,
-    explicit boolean NOT NULL,
-    infestationstatus integer NOT NULL,
-    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
-    creator integer NOT NULL,
-    dateverified timestamp without time zone,
-    verifiedby integer,
-    lastmodified timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
-    lastmodifiedby integer NOT NULL
-);
-
-CREATE SEQUENCE bugpackageinfestation_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE bugpackageinfestation_id_seq OWNED BY bugpackageinfestation.id;
-
-CREATE TABLE bugproductinfestation (
-    id integer NOT NULL,
-    bug integer NOT NULL,
-    productrelease integer NOT NULL,
-    explicit boolean NOT NULL,
-    infestationstatus integer NOT NULL,
-    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
-    creator integer NOT NULL,
-    dateverified timestamp without time zone,
-    verifiedby integer,
-    lastmodified timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
-    lastmodifiedby integer NOT NULL
-);
-
-CREATE SEQUENCE bugproductinfestation_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE bugproductinfestation_id_seq OWNED BY bugproductinfestation.id;
-
-CREATE TABLE bugsubscription (
-    id integer NOT NULL,
-    person integer NOT NULL,
-    bug integer NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    subscribed_by integer NOT NULL,
-    bug_notification_level integer DEFAULT 40 NOT NULL
-);
 
 CREATE SEQUENCE bugsubscription_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugsubscription_id_seq OWNED BY bugsubscription.id;
 
-CREATE TABLE bugtag (
-    id integer NOT NULL,
-    bug integer NOT NULL,
+
+CREATE TABLE bugsubscriptionfilter (
+    id integer NOT NULL,
+    structuralsubscription integer,
+    find_all_tags boolean NOT NULL,
+    include_any_tags boolean NOT NULL,
+    exclude_any_tags boolean NOT NULL,
+    other_parameters text,
+    description text,
+    bug_notification_level integer DEFAULT 40 NOT NULL
+);
+
+
+COMMENT ON TABLE bugsubscriptionfilter IS 'A filter with search criteria. Emails are sent only if the affected bug matches the specified parameters. The parameters are the same as those used for bugtask searches.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilter.structuralsubscription IS 'The structural subscription to be filtered.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilter.find_all_tags IS 'If set, search for bugs having all tags specified in BugSubscriptionFilterTag, else search for bugs having any of the tags specified in BugSubscriptionFilterTag.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilter.include_any_tags IS 'If True, include messages for bugs having any tag set.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilter.exclude_any_tags IS 'If True, exclude bugs having any tag set.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilter.other_parameters IS 'Other filter paremeters. Actual filtering is implemented on Python level.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilter.description IS 'A description of the filter, allowing subscribers to note the intent of the filter.';
+
+
+CREATE SEQUENCE bugsubscriptionfilter_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE bugsubscriptionfilter_id_seq OWNED BY bugsubscriptionfilter.id;
+
+
+CREATE TABLE bugsubscriptionfilterimportance (
+    id integer NOT NULL,
+    filter integer NOT NULL,
+    importance integer NOT NULL
+);
+
+
+COMMENT ON TABLE bugsubscriptionfilterimportance IS 'Filter a bugsubscription by bug task status.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilterimportance.filter IS 'The subscription filter of this record.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilterimportance.importance IS 'The bug task importance.';
+
+
+CREATE SEQUENCE bugsubscriptionfilterimportance_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE bugsubscriptionfilterimportance_id_seq OWNED BY bugsubscriptionfilterimportance.id;
+
+
+CREATE TABLE bugsubscriptionfiltermute (
+    person integer NOT NULL,
+    filter integer NOT NULL,
+    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
+);
+
+
+CREATE TABLE bugsubscriptionfilterstatus (
+    id integer NOT NULL,
+    filter integer NOT NULL,
+    status integer NOT NULL
+);
+
+
+COMMENT ON TABLE bugsubscriptionfilterstatus IS 'Filter a bugsubscription by bug task status.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilterstatus.filter IS 'The subscription filter of this record.';
+
+
+COMMENT ON COLUMN bugsubscriptionfilterstatus.status IS 'The bug task status.';
+
+
+CREATE SEQUENCE bugsubscriptionfilterstatus_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE bugsubscriptionfilterstatus_id_seq OWNED BY bugsubscriptionfilterstatus.id;
+
+
+CREATE TABLE bugsubscriptionfiltertag (
+    id integer NOT NULL,
+    filter integer NOT NULL,
     tag text NOT NULL,
-    CONSTRAINT valid_tag CHECK (valid_name(tag))
-);
+    include boolean NOT NULL
+);
+
+
+COMMENT ON TABLE bugsubscriptionfiltertag IS 'Filter by bug tag.';
+
+
+COMMENT ON COLUMN bugsubscriptionfiltertag.filter IS 'The subscription filter of this record.';
+
+
+COMMENT ON COLUMN bugsubscriptionfiltertag.tag IS 'A bug tag.';
+
+
+COMMENT ON COLUMN bugsubscriptionfiltertag.include IS 'If True, send only messages for bugs having this tag, else send only messages for bugs which do not have this tag.';
+
+
+CREATE SEQUENCE bugsubscriptionfiltertag_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE bugsubscriptionfiltertag_id_seq OWNED BY bugsubscriptionfiltertag.id;
+
+
+CREATE SEQUENCE bugsummary_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE bugsummary_id_seq OWNED BY bugsummary.id;
+
+
+CREATE TABLE bugsummaryjournal (
+    id integer NOT NULL,
+    count integer DEFAULT 0 NOT NULL,
+    product integer,
+    productseries integer,
+    distribution integer,
+    distroseries integer,
+    sourcepackagename integer,
+    viewed_by integer,
+    tag text,
+    status integer NOT NULL,
+    milestone integer,
+    importance integer NOT NULL,
+    has_patch boolean NOT NULL,
+    fixed_upstream boolean NOT NULL
+);
+
+
+CREATE SEQUENCE bugsummaryjournal_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE bugsummaryjournal_id_seq OWNED BY bugsummaryjournal.id;
+
 
 CREATE SEQUENCE bugtag_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugtag_id_seq OWNED BY bugtag.id;
 
-CREATE TABLE bugtask (
-    id integer NOT NULL,
-    bug integer NOT NULL,
-    product integer,
-    distribution integer,
-    distroseries integer,
-    sourcepackagename integer,
-    binarypackagename integer,
-    status integer NOT NULL,
-    priority integer,
-    importance integer DEFAULT 5 NOT NULL,
-    assignee integer,
-    date_assigned timestamp without time zone,
-    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone),
-    owner integer NOT NULL,
-    milestone integer,
-    bugwatch integer,
-    statusexplanation text,
-    fti ts2.tsvector,
-    targetnamecache text,
-    date_confirmed timestamp without time zone,
-    date_inprogress timestamp without time zone,
-    date_closed timestamp without time zone,
-    productseries integer,
-    date_incomplete timestamp without time zone,
-    date_left_new timestamp without time zone,
-    date_triaged timestamp without time zone,
-    date_fix_committed timestamp without time zone,
-    date_fix_released timestamp without time zone,
-    date_left_closed timestamp without time zone,
-    heat_rank integer DEFAULT 0 NOT NULL,
-    date_milestone_set timestamp without time zone,
-    CONSTRAINT bugtask_assignment_checks CHECK (CASE WHEN (product IS NOT NULL) THEN ((((productseries IS NULL) AND (distribution IS NULL)) AND (distroseries IS NULL)) AND (sourcepackagename IS NULL)) WHEN (productseries IS NOT NULL) THEN (((distribution IS NULL) AND (distroseries IS NULL)) AND (sourcepackagename IS NULL)) WHEN (distribution IS NOT NULL) THEN (distroseries IS NULL) WHEN (distroseries IS NOT NULL) THEN true ELSE false END)
-);
 
 CREATE SEQUENCE bugtask_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugtask_id_seq OWNED BY bugtask.id;
 
+
 CREATE TABLE bugtracker (
     id integer NOT NULL,
     bugtrackertype integer NOT NULL,
@@ -1292,28 +6932,149 @@
     CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
+
+COMMENT ON TABLE bugtracker IS 'A bug tracker in some other project. Malone allows us to link Malone bugs with bugs recorded in other bug tracking systems, and to keep the status of the relevant bug task in sync with the status in that upstream bug tracker. So, for example, you might note that Malone bug #43224 is the same as a bug in the Apache bugzilla, number 534536. Then when the upstream guys mark that bug fixed in their bugzilla, Malone know that the bug is fixed upstream.';
+
+
+COMMENT ON COLUMN bugtracker.bugtrackertype IS 'The type of bug tracker, a pointer to the table of bug tracker types. Currently we know about debbugs and bugzilla bugtrackers, and plan to support roundup and sourceforge as well.';
+
+
+COMMENT ON COLUMN bugtracker.name IS 'The unique name of this bugtracker, allowing us to refer to it directly.';
+
+
+COMMENT ON COLUMN bugtracker.title IS 'A title for the bug tracker, used in listings of all the bug trackers and also displayed at the top of the descriptive page for the bug tracker.';
+
+
+COMMENT ON COLUMN bugtracker.summary IS 'A brief summary of this bug tracker, which might for example list any interesting policies regarding the use of the bug tracker. The summary is displayed in bold at the top of the bug tracker page.';
+
+
+COMMENT ON COLUMN bugtracker.baseurl IS 'The base URL for this bug tracker. Using our knowledge of the bugtrackertype, and the details in the BugWatch table we are then able to calculate relative URLs for relevant pages in the bug tracker based on this baseurl.';
+
+
+COMMENT ON COLUMN bugtracker.owner IS 'The person who created this bugtracker entry and who thus has permission to modify it. Ideally we would like this to be the person who coordinates the running of the actual bug tracker upstream.';
+
+
+COMMENT ON COLUMN bugtracker.contactdetails IS 'The contact details of the people responsible for that bug tracker. This allows us to coordinate the syncing of bugs to and from that bug tracker with the responsible people on the other side.';
+
+
+COMMENT ON COLUMN bugtracker.version IS 'The version of the bug tracker software being used.';
+
+
+COMMENT ON COLUMN bugtracker.block_comment_pushing IS 'Whether to block pushing comments to the bug tracker. Having a value of false means that we will push the comments if the bug tracker supports it.';
+
+
+COMMENT ON COLUMN bugtracker.has_lp_plugin IS 'Whether we have confirmed that the Launchpad plugin was installed on the bug tracker, the last time checkwatches was run.';
+
+
 CREATE SEQUENCE bugtracker_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugtracker_id_seq OWNED BY bugtracker.id;
 
+
 CREATE TABLE bugtrackeralias (
     id integer NOT NULL,
     bugtracker integer NOT NULL,
     base_url text NOT NULL
 );
 
+
+COMMENT ON TABLE bugtrackeralias IS 'A bugtracker alias is a URL that also refers to the same bugtracker as the master bugtracker. For example, a bugtracker might be accessible as both http://www.bugsrus.com/ and http://bugsrus.com/. A bugtracker can have many aliases, and all of them are checked to prevents users registering duplicate bugtrackers inadvertently.';
+
+
+COMMENT ON COLUMN bugtrackeralias.bugtracker IS 'The master bugtracker that this alias refers to.';
+
+
+COMMENT ON COLUMN bugtrackeralias.base_url IS 'Another base URL for this bug tracker. See BugTracker.baseurl.';
+
+
 CREATE SEQUENCE bugtrackeralias_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugtrackeralias_id_seq OWNED BY bugtrackeralias.id;
 
+
+CREATE TABLE bugtrackercomponent (
+    id integer NOT NULL,
+    name text NOT NULL,
+    is_visible boolean DEFAULT true NOT NULL,
+    is_custom boolean DEFAULT true NOT NULL,
+    component_group integer NOT NULL,
+    distribution integer,
+    source_package_name integer,
+    CONSTRAINT valid_target CHECK (((distribution IS NULL) = (source_package_name IS NULL)))
+);
+
+
+COMMENT ON TABLE bugtrackercomponent IS 'A software component in a remote bug tracker, which can be linked to the corresponding source package in a distribution using this table.';
+
+
+COMMENT ON COLUMN bugtrackercomponent.name IS 'The name of the component as registered in the remote bug tracker.';
+
+
+COMMENT ON COLUMN bugtrackercomponent.is_visible IS 'Whether to display or hide the item in the Launchpad user interface.';
+
+
+COMMENT ON COLUMN bugtrackercomponent.is_custom IS 'Whether the item was added by a user in Launchpad or is kept in sync with the remote bug tracker.';
+
+
+COMMENT ON COLUMN bugtrackercomponent.component_group IS 'The product or other higher level category used by the remote bug tracker to group projects, if any.';
+
+
+COMMENT ON COLUMN bugtrackercomponent.distribution IS 'Link to the distribution for the associated source package.  This can be NULL if no ling has been established.';
+
+
+COMMENT ON COLUMN bugtrackercomponent.source_package_name IS 'The text name of the source package in a distribution that corresponds to this component.  This can be NULL if no link has been established yet.';
+
+
+CREATE SEQUENCE bugtrackercomponent_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE bugtrackercomponent_id_seq OWNED BY bugtrackercomponent.id;
+
+
+CREATE TABLE bugtrackercomponentgroup (
+    id integer NOT NULL,
+    name text NOT NULL,
+    bug_tracker integer NOT NULL
+);
+
+
+COMMENT ON TABLE bugtrackercomponentgroup IS 'A collection of components as modeled in a remote bug tracker, often referred to as a product.  Some bug trackers do not categorize software components this way, so they will have a single default component group that all components belong to.';
+
+
+COMMENT ON COLUMN bugtrackercomponentgroup.name IS 'The product or category name used in the remote bug tracker for grouping components.';
+
+
+COMMENT ON COLUMN bugtrackercomponentgroup.bug_tracker IS 'The external bug tracker this component group belongs to.';
+
+
+CREATE SEQUENCE bugtrackercomponentgroup_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE bugtrackercomponentgroup_id_seq OWNED BY bugtrackercomponentgroup.id;
+
+
 CREATE TABLE bugtrackerperson (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -1322,14 +7083,33 @@
     name text NOT NULL
 );
 
+
+COMMENT ON TABLE bugtrackerperson IS 'A mapping from a user in an external bug tracker to a Person record in Launchpad. This is used when we can''t get an e-mail address from the bug tracker.';
+
+
+COMMENT ON COLUMN bugtrackerperson.date_created IS 'When was this mapping added.';
+
+
+COMMENT ON COLUMN bugtrackerperson.bugtracker IS 'The external bug tracker in which this user has an account.';
+
+
+COMMENT ON COLUMN bugtrackerperson.person IS 'The Person record in Launchpad this user corresponds to.';
+
+
+COMMENT ON COLUMN bugtrackerperson.name IS 'The (within the bug tracker) unique username in the external bug tracker.';
+
+
 CREATE SEQUENCE bugtrackerperson_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugtrackerperson_id_seq OWNED BY bugtrackerperson.id;
 
+
 CREATE TABLE bugwatch (
     id integer NOT NULL,
     bug integer NOT NULL,
@@ -1346,14 +7126,30 @@
     next_check timestamp without time zone
 );
 
+
+COMMENT ON COLUMN bugwatch.last_error_type IS 'The type of error which last prevented this entry from being updated. Legal values are defined by the BugWatchErrorType enumeration.';
+
+
+COMMENT ON COLUMN bugwatch.remote_importance IS 'The importance of the bug as returned by the remote server. This will be converted into a Launchpad BugTaskImportance value.';
+
+
+COMMENT ON COLUMN bugwatch.remote_lp_bug_id IS 'The bug in Launchpad that the remote bug is pointing at. This can be different than the BugWatch.bug column, since the same remote bug can be linked from multiple bugs in Launchpad, but the remote bug can only link to a single bug in Launchpad. The main use case for this column is to avoid having to query the remote bug tracker for this information, in order to decide whether we need to give this information to the remote bug tracker.';
+
+
+COMMENT ON COLUMN bugwatch.next_check IS 'The time after which the watch should next be checked. Note that this does not denote an exact schedule for the next check since checkwatches only runs periodically.';
+
+
 CREATE SEQUENCE bugwatch_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugwatch_id_seq OWNED BY bugwatch.id;
 
+
 CREATE TABLE bugwatchactivity (
     id integer NOT NULL,
     bug_watch integer NOT NULL,
@@ -1361,16 +7157,39 @@
     result integer NOT NULL,
     message text,
     oops_id text
-);
+)
+WITH (fillfactor=100);
+
+
+COMMENT ON TABLE bugwatchactivity IS 'This table contains a record of each update for a given bug watch. This allows us to track whether a given update was successful or not and, if not, the details of the error which caused the update to fail.';
+
+
+COMMENT ON COLUMN bugwatchactivity.bug_watch IS 'The bug_watch to which this activity entry relates.';
+
+
+COMMENT ON COLUMN bugwatchactivity.activity_date IS 'The datetime at which the activity occurred.';
+
+
+COMMENT ON COLUMN bugwatchactivity.result IS 'The result of the update. Legal values are defined in the BugWatchErrorType enumeration. An update is considered successful if its error_type is NULL.';
+
+
+COMMENT ON COLUMN bugwatchactivity.message IS 'The message (if any) associated with the update.';
+
+
+COMMENT ON COLUMN bugwatchactivity.oops_id IS 'The OOPS id, if any, associated with the error that caused the update to fail.';
+
 
 CREATE SEQUENCE bugwatchactivity_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE bugwatchactivity_id_seq OWNED BY bugwatchactivity.id;
 
+
 CREATE TABLE builder (
     id integer NOT NULL,
     processor integer NOT NULL,
@@ -1387,17 +7206,52 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     vm_host text,
     active boolean DEFAULT true NOT NULL,
+    failure_count integer DEFAULT 0 NOT NULL,
     CONSTRAINT valid_absolute_url CHECK (valid_absolute_url(url))
 );
 
+
+COMMENT ON TABLE builder IS 'Builder: This table stores the build-slave registry and status information as: name, url, trusted, builderok, builderaction, failnotes.';
+
+
+COMMENT ON COLUMN builder.speedindex IS 'A relative measure of the speed of this builder. If NULL, we do not yet have a speedindex for the builder else it is the number of seconds needed to perform a reference build';
+
+
+COMMENT ON COLUMN builder.builderok IS 'Should a builder fail for any reason, from out-of-disk-space to not responding to the buildd master, the builderok flag is set to false and the failnotes column is filled with a reason.';
+
+
+COMMENT ON COLUMN builder.failnotes IS 'This column gets filled out with a textual description of how/why a builder has failed. If the builderok column is true then the value in this column is irrelevant and should be treated as NULL or empty.';
+
+
+COMMENT ON COLUMN builder.virtualized IS 'Whether or not the builder is a virtual Xen builder. Packages coming via ubuntu workflow are trusted to build on non-Xen and do not need facist behaviour to be built. Other packages like ppa/grumpy incoming packages can contain malicious code, so are unstrusted and build in a Xen virtual machine.';
+
+
+COMMENT ON COLUMN builder.url IS 'The url to the build slave. There may be more than one build slave on a given host so this url includes the port number to use. The default port number for a build slave is 8221';
+
+
+COMMENT ON COLUMN builder.manual IS 'Whether or not builder was manual mode, i.e., collect any result from the it, but do not dispach anything to it automatically.';
+
+
+COMMENT ON COLUMN builder.vm_host IS 'The virtual machine host associated to this builder. It should be empty for "native" builders (old fashion or architectures not yet supported by XEN).';
+
+
+COMMENT ON COLUMN builder.active IS 'Whether to present or not the builder in the public list of builders avaialble. It is used to hide transient or defunct builders while they get fixed.';
+
+
+COMMENT ON COLUMN builder.failure_count IS 'The number of consecutive failures on this builder.  Is reset to zero after a sucessful dispatch.';
+
+
 CREATE SEQUENCE builder_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE builder_id_seq OWNED BY builder.id;
 
+
 CREATE TABLE buildfarmjob (
     id integer NOT NULL,
     processor integer,
@@ -1410,31 +7264,76 @@
     status integer NOT NULL,
     log integer,
     job_type integer NOT NULL,
+    failure_count integer DEFAULT 0 NOT NULL,
     CONSTRAINT started_if_finished CHECK (((date_finished IS NULL) OR (date_started IS NOT NULL)))
 );
 
+
+COMMENT ON TABLE buildfarmjob IS 'BuildFarmJob: This table stores the information common to all jobs on the Launchpad build farm.';
+
+
+COMMENT ON COLUMN buildfarmjob.processor IS 'Points to the required processor target for this job, or null.';
+
+
+COMMENT ON COLUMN buildfarmjob.virtualized IS 'The virtualization setting required by this build farm job, or null.';
+
+
+COMMENT ON COLUMN buildfarmjob.date_created IS 'When the build farm job record was created.';
+
+
+COMMENT ON COLUMN buildfarmjob.date_started IS 'When the build farm job started being processed.';
+
+
+COMMENT ON COLUMN buildfarmjob.date_finished IS 'When the build farm job finished being processed.';
+
+
+COMMENT ON COLUMN buildfarmjob.date_first_dispatched IS 'The instant the build was dispatched the first time. This value will not get overridden if the build is retried.';
+
+
+COMMENT ON COLUMN buildfarmjob.builder IS 'Points to the builder which processed this build farm job.';
+
+
+COMMENT ON COLUMN buildfarmjob.status IS 'Stores the current build status.';
+
+
+COMMENT ON COLUMN buildfarmjob.log IS 'Points to the log for this build farm job file stored in librarian.';
+
+
+COMMENT ON COLUMN buildfarmjob.job_type IS 'The type of build farm job to which this record corresponds.';
+
+
+COMMENT ON COLUMN buildfarmjob.failure_count IS 'The number of consecutive failures on this job.  If excessive, the job may be terminated.';
+
+
 CREATE SEQUENCE buildfarmjob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE buildfarmjob_id_seq OWNED BY buildfarmjob.id;
 
+
 CREATE TABLE buildpackagejob (
     id integer NOT NULL,
     job integer NOT NULL,
     build integer NOT NULL
 );
 
+
 CREATE SEQUENCE buildpackagejob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE buildpackagejob_id_seq OWNED BY buildpackagejob.id;
 
+
 CREATE TABLE buildqueue (
     id integer NOT NULL,
     builder integer,
@@ -1448,14 +7347,48 @@
     virtualized boolean
 );
 
+
+COMMENT ON TABLE buildqueue IS 'BuildQueue: The queue of jobs in progress/scheduled to run on the Soyuz build farm.';
+
+
+COMMENT ON COLUMN buildqueue.builder IS 'The builder assigned to this build. Some builds will have a builder assigned to queue them up; some will be building on the specified builder already; others will not have a builder yet (NULL) and will be waiting to be assigned into a builder''s queue';
+
+
+COMMENT ON COLUMN buildqueue.logtail IS 'The tail end of the log of the current build. This is updated regularly as the buildd master polls the buildd slaves. Once the build is complete; the full log will be lodged with the librarian and linked into the build table.';
+
+
+COMMENT ON COLUMN buildqueue.lastscore IS 'The last score ascribed to this build record. This can be used in the UI among other places.';
+
+
+COMMENT ON COLUMN buildqueue.manual IS 'Indicates if the current record was or not rescored manually, if so it get skipped from the auto-score procedure.';
+
+
+COMMENT ON COLUMN buildqueue.job IS 'Foreign key to the `Job` table row with the generic job data.';
+
+
+COMMENT ON COLUMN buildqueue.job_type IS 'Type of job (enumeration value), enables us to find/query the correct table with the data specific to this type of job.';
+
+
+COMMENT ON COLUMN buildqueue.estimated_duration IS 'Estimated job duration, based on previous running times of comparable jobs.';
+
+
+COMMENT ON COLUMN buildqueue.processor IS 'The processor required by the associated build farm job.';
+
+
+COMMENT ON COLUMN buildqueue.virtualized IS 'The virtualization setting required by the associated build farm job.';
+
+
 CREATE SEQUENCE buildqueue_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE buildqueue_id_seq OWNED BY buildqueue.id;
 
+
 CREATE TABLE codeimport (
     id integer NOT NULL,
     branch integer NOT NULL,
@@ -1470,17 +7403,57 @@
     assignee integer,
     update_interval interval,
     url text,
-    CONSTRAINT valid_vcs_details CHECK (CASE WHEN (rcs_type = 1) THEN (((((cvs_root IS NOT NULL) AND (cvs_root <> ''::text)) AND (cvs_module IS NOT NULL)) AND (cvs_module <> ''::text)) AND (url IS NULL)) WHEN (rcs_type = ANY (ARRAY[2, 3])) THEN ((((cvs_root IS NULL) AND (cvs_module IS NULL)) AND (url IS NOT NULL)) AND valid_absolute_url(url)) WHEN (rcs_type = ANY (ARRAY[4, 5])) THEN (((cvs_root IS NULL) AND (cvs_module IS NULL)) AND (url IS NOT NULL)) ELSE false END)
+    CONSTRAINT valid_vcs_details CHECK (CASE WHEN (rcs_type = 1) THEN (((((cvs_root IS NOT NULL) AND (cvs_root <> ''::text)) AND (cvs_module IS NOT NULL)) AND (cvs_module <> ''::text)) AND (url IS NULL)) WHEN (rcs_type = ANY (ARRAY[2, 3])) THEN ((((cvs_root IS NULL) AND (cvs_module IS NULL)) AND (url IS NOT NULL)) AND valid_absolute_url(url)) WHEN (rcs_type = ANY (ARRAY[4, 5, 6])) THEN (((cvs_root IS NULL) AND (cvs_module IS NULL)) AND (url IS NOT NULL)) ELSE false END)
 );
 
+
+COMMENT ON TABLE codeimport IS 'The persistent record of an import from a foreign version control system to Bazaar, from the initial request to the regularly updated import branch.';
+
+
+COMMENT ON COLUMN codeimport.branch IS 'The Bazaar branch produced by the import system.  Always non-NULL: a placeholder branch is created when the import is created.  The import is associated to a Product and Series though the branch.';
+
+
+COMMENT ON COLUMN codeimport.registrant IS 'The person who originally requested this import.';
+
+
+COMMENT ON COLUMN codeimport.rcs_type IS 'The revision control system used by the import source. The value is defined in dbschema.RevisionControlSystems.';
+
+
+COMMENT ON COLUMN codeimport.cvs_root IS 'The $CVSROOT details, probably of the form :pserver:user@host:/path.';
+
+
+COMMENT ON COLUMN codeimport.cvs_module IS 'The module in cvs_root to import, often the name of the project.';
+
+
+COMMENT ON COLUMN codeimport.review_status IS 'Whether this code import request has been reviewed, and whether it was accepted.';
+
+
+COMMENT ON COLUMN codeimport.date_last_successful IS 'When this code import last succeeded. NULL if this import has never succeeded.';
+
+
+COMMENT ON COLUMN codeimport.owner IS 'The person who is currently responsible for keeping the import details up to date, initially set to the registrant. This person can edit some of the details of the code import branch.';
+
+
+COMMENT ON COLUMN codeimport.assignee IS 'The person in charge of delivering this code import and interacting with the owner.';
+
+
+COMMENT ON COLUMN codeimport.update_interval IS 'How often should this import be updated. If NULL, defaults to a system-wide value set by the Launchpad administrators.';
+
+
+COMMENT ON COLUMN codeimport.url IS 'The URL of the foreign VCS branch for this import.';
+
+
 CREATE SEQUENCE codeimport_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE codeimport_id_seq OWNED BY codeimport.id;
 
+
 CREATE TABLE codeimportevent (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -1490,14 +7463,33 @@
     machine integer
 );
 
+
+COMMENT ON TABLE codeimportevent IS 'A record of events in the code import system.  Rows in this table are created by triggers on other code import tables.';
+
+
+COMMENT ON COLUMN codeimportevent.entry_type IS 'The type of event that is recorded by this entry. Legal values are defined by the CodeImportEventType enumeration.';
+
+
+COMMENT ON COLUMN codeimportevent.code_import IS 'The code import that was associated to this event, if any and if it has not been deleted.';
+
+
+COMMENT ON COLUMN codeimportevent.person IS 'The user who caused the event, if the event is not automatically generated.';
+
+
+COMMENT ON COLUMN codeimportevent.machine IS 'The code import machine that was concerned by this event, if any.';
+
+
 CREATE SEQUENCE codeimportevent_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE codeimportevent_id_seq OWNED BY codeimportevent.id;
 
+
 CREATE TABLE codeimporteventdata (
     id integer NOT NULL,
     event integer,
@@ -1505,14 +7497,30 @@
     data_value text
 );
 
+
+COMMENT ON TABLE codeimporteventdata IS 'Additional data associated to a particular code import event.';
+
+
+COMMENT ON COLUMN codeimporteventdata.event IS 'The event the data is associated to.';
+
+
+COMMENT ON COLUMN codeimporteventdata.data_type IS 'The type of additional data, from the CodeImportEventDataType enumeration.';
+
+
+COMMENT ON COLUMN codeimporteventdata.data_value IS 'The value of the additional data.  A string.';
+
+
 CREATE SEQUENCE codeimporteventdata_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE codeimporteventdata_id_seq OWNED BY codeimporteventdata.id;
 
+
 CREATE TABLE codeimportjob (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -1528,14 +7536,48 @@
     CONSTRAINT valid_state CHECK (CASE WHEN (state = 10) THEN (((((machine IS NULL) AND (ordering IS NULL)) AND (heartbeat IS NULL)) AND (date_started IS NULL)) AND (logtail IS NULL)) WHEN (state = 20) THEN (((((machine IS NOT NULL) AND (ordering IS NOT NULL)) AND (heartbeat IS NULL)) AND (date_started IS NULL)) AND (logtail IS NULL)) WHEN (state = 30) THEN (((((machine IS NOT NULL) AND (ordering IS NULL)) AND (heartbeat IS NOT NULL)) AND (date_started IS NOT NULL)) AND (logtail IS NOT NULL)) ELSE false END)
 );
 
+
+COMMENT ON TABLE codeimportjob IS 'A pending or active code import job.  There is always such a row for any active import, but it will not run until date_due is in the past.';
+
+
+COMMENT ON COLUMN codeimportjob.code_import IS 'The code import that is being worked upon.';
+
+
+COMMENT ON COLUMN codeimportjob.machine IS 'The machine job is currently scheduled to run on, or where the job is currently running.';
+
+
+COMMENT ON COLUMN codeimportjob.date_due IS 'When the import should happen.';
+
+
+COMMENT ON COLUMN codeimportjob.state IS 'One of PENDING (waiting until its due or a machine is online), SCHEDULED (assigned to a machine, but not yet running) or RUNNING (actually in the process of being imported now).';
+
+
+COMMENT ON COLUMN codeimportjob.requesting_user IS 'The user who requested the import, if any. Set if and only if reason = REQUEST.';
+
+
+COMMENT ON COLUMN codeimportjob.ordering IS 'A measure of how urgent the job is -- queue entries with lower "ordering" should be processed first, or in other works "ORDER BY ordering" returns the most import jobs first.';
+
+
+COMMENT ON COLUMN codeimportjob.heartbeat IS 'While the job is running, this field should be updated frequently to indicate that the import job hasn''t crashed.';
+
+
+COMMENT ON COLUMN codeimportjob.logtail IS 'The last few lines of output produced by the running job. It should be updated at the same time as the heartbeat.';
+
+
+COMMENT ON COLUMN codeimportjob.date_started IS 'When the import began to be processed.';
+
+
 CREATE SEQUENCE codeimportjob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE codeimportjob_id_seq OWNED BY codeimportjob.id;
 
+
 CREATE TABLE codeimportmachine (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -1544,14 +7586,30 @@
     heartbeat timestamp without time zone
 );
 
+
+COMMENT ON TABLE codeimportmachine IS 'The record of a machine capable of performing jobs for the code import system.';
+
+
+COMMENT ON COLUMN codeimportmachine.hostname IS 'The (unique) hostname of the machine.';
+
+
+COMMENT ON COLUMN codeimportmachine.state IS 'Whether the controller daemon on this machine is offline, online, or quiescing (running but not accepting new jobs).';
+
+
+COMMENT ON COLUMN codeimportmachine.heartbeat IS 'When the code-import-controller daemon was last known to be running on this machine. If it is not updated for a long time the machine state will change to offline.';
+
+
 CREATE SEQUENCE codeimportmachine_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE codeimportmachine_id_seq OWNED BY codeimportmachine.id;
 
+
 CREATE TABLE codeimportresult (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -1564,14 +7622,39 @@
     date_job_started timestamp without time zone
 );
 
+
+COMMENT ON TABLE codeimportresult IS 'A completed code import job.';
+
+
+COMMENT ON COLUMN codeimportresult.code_import IS 'The code import for which the job was run.';
+
+
+COMMENT ON COLUMN codeimportresult.machine IS 'The machine the job ran on.';
+
+
+COMMENT ON COLUMN codeimportresult.log_excerpt IS 'The last few lines of the partial log, in case it is set.';
+
+
+COMMENT ON COLUMN codeimportresult.log_file IS 'A partial log of the job for users to see. It is normally only recorded if the job failed in a step that interacts with the remote repository. If a job was successful, or failed in a houskeeping step, the log file would not contain information useful to the user.';
+
+
+COMMENT ON COLUMN codeimportresult.status IS 'How the job ended. Success, some kind of failure, or some kind of interruption before completion.';
+
+
+COMMENT ON COLUMN codeimportresult.date_job_started IS 'When the job started to run (date_created is when it finished).';
+
+
 CREATE SEQUENCE codeimportresult_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE codeimportresult_id_seq OWNED BY codeimportresult.id;
 
+
 CREATE TABLE codereviewmessage (
     id integer NOT NULL,
     branch_merge_proposal integer NOT NULL,
@@ -1580,14 +7663,33 @@
     vote_tag text
 );
 
+
+COMMENT ON TABLE codereviewmessage IS 'A message that is part of a code review discussion.';
+
+
+COMMENT ON COLUMN codereviewmessage.branch_merge_proposal IS 'The merge proposal that is being discussed.';
+
+
+COMMENT ON COLUMN codereviewmessage.message IS 'The actual message.';
+
+
+COMMENT ON COLUMN codereviewmessage.vote IS 'The reviewer''s vote for this message.';
+
+
+COMMENT ON COLUMN codereviewmessage.vote_tag IS 'A short description of the vote';
+
+
 CREATE SEQUENCE codereviewmessage_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE codereviewmessage_id_seq OWNED BY codereviewmessage.id;
 
+
 CREATE TABLE codereviewvote (
     id integer NOT NULL,
     branch_merge_proposal integer NOT NULL,
@@ -1598,14 +7700,43 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE codereviewvote IS 'Reference to a person''s last vote in a code review discussion.';
+
+
+COMMENT ON COLUMN codereviewvote.branch_merge_proposal IS 'The BranchMergeProposal for the code review.';
+
+
+COMMENT ON COLUMN codereviewvote.reviewer IS 'The person performing the review.';
+
+
+COMMENT ON COLUMN codereviewvote.review_type IS 'The aspect of the code being reviewed.';
+
+
+COMMENT ON COLUMN codereviewvote.registrant IS 'The person who registered this vote';
+
+
+COMMENT ON COLUMN codereviewvote.vote_message IS 'The message associated with the vote';
+
+
+COMMENT ON COLUMN codereviewvote.date_created IS 'The date this vote reference was created';
+
+
 CREATE SEQUENCE codereviewvote_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE codereviewvote_id_seq OWNED BY codereviewvote.id;
 
+
+CREATE VIEW combinedbugsummary AS
+    SELECT bugsummary.id, bugsummary.count, bugsummary.product, bugsummary.productseries, bugsummary.distribution, bugsummary.distroseries, bugsummary.sourcepackagename, bugsummary.viewed_by, bugsummary.tag, bugsummary.status, bugsummary.milestone, bugsummary.importance, bugsummary.has_patch, bugsummary.fixed_upstream FROM bugsummary UNION ALL SELECT (- bugsummaryjournal.id) AS id, bugsummaryjournal.count, bugsummaryjournal.product, bugsummaryjournal.productseries, bugsummaryjournal.distribution, bugsummaryjournal.distroseries, bugsummaryjournal.sourcepackagename, bugsummaryjournal.viewed_by, bugsummaryjournal.tag, bugsummaryjournal.status, bugsummaryjournal.milestone, bugsummaryjournal.importance, bugsummaryjournal.has_patch, bugsummaryjournal.fixed_upstream FROM bugsummaryjournal;
+
+
 CREATE TABLE commercialsubscription (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -1620,22 +7751,62 @@
     sales_system_id text
 );
 
+
+COMMENT ON TABLE commercialsubscription IS 'A Commercial Subscription entry for a project.  Projects with licenses of Other/Proprietary must purchase a subscription in order to use Launchpad.';
+
+
+COMMENT ON COLUMN commercialsubscription.date_created IS 'The date this subscription was created in Launchpad.';
+
+
+COMMENT ON COLUMN commercialsubscription.date_last_modified IS 'The date this subscription was last modified.';
+
+
+COMMENT ON COLUMN commercialsubscription.date_starts IS 'The beginning date for this subscription.  It is invalid until that date.';
+
+
+COMMENT ON COLUMN commercialsubscription.date_expires IS 'The expiration date for this subscription.  It is invalid after that date.';
+
+
+COMMENT ON COLUMN commercialsubscription.status IS 'The current status.  One of: SUBSCRIBED, LAPSED, SUSPENDED.';
+
+
+COMMENT ON COLUMN commercialsubscription.product IS 'The product this subscription enables.';
+
+
+COMMENT ON COLUMN commercialsubscription.registrant IS 'The person who created this subscription.';
+
+
+COMMENT ON COLUMN commercialsubscription.purchaser IS 'The person who purchased this subscription.';
+
+
+COMMENT ON COLUMN commercialsubscription.whiteboard IS 'A place for administrators to store comments related to this subscription.';
+
+
+COMMENT ON COLUMN commercialsubscription.sales_system_id IS 'A reference in the external sales system (e.g. Salesforce) that can be used to identify this subscription.';
+
+
 CREATE SEQUENCE commercialsubscription_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE commercialsubscription_id_seq OWNED BY commercialsubscription.id;
 
+
 CREATE SEQUENCE component_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE component_id_seq OWNED BY component.id;
 
+
 CREATE TABLE componentselection (
     id integer NOT NULL,
     distroseries integer NOT NULL,
@@ -1643,28 +7814,55 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE componentselection IS 'Allowed components in a given distroseries.';
+
+
+COMMENT ON COLUMN componentselection.distroseries IS 'Refers to the distroseries in question.';
+
+
+COMMENT ON COLUMN componentselection.component IS 'Refers to the component in qestion.';
+
+
 CREATE SEQUENCE componentselection_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE componentselection_id_seq OWNED BY componentselection.id;
 
+
 CREATE TABLE continent (
     id integer NOT NULL,
     code text NOT NULL,
     name text NOT NULL
-);
+)
+WITH (fillfactor=100);
+
+
+COMMENT ON TABLE continent IS 'A continent in this huge world.';
+
+
+COMMENT ON COLUMN continent.code IS 'A two-letter code for a continent.';
+
+
+COMMENT ON COLUMN continent.name IS 'The name of the continent.';
+
 
 CREATE SEQUENCE continent_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE continent_id_seq OWNED BY continent.id;
 
+
 CREATE TABLE country (
     id integer NOT NULL,
     iso3166code2 character(2) NOT NULL,
@@ -1673,16 +7871,21 @@
     title text,
     description text,
     continent integer NOT NULL
-);
+)
+WITH (fillfactor=100);
+
 
 CREATE SEQUENCE country_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE country_id_seq OWNED BY country.id;
 
+
 CREATE TABLE customlanguagecode (
     id integer NOT NULL,
     product integer,
@@ -1694,14 +7897,36 @@
     CONSTRAINT product_or_distro CHECK (((product IS NULL) <> (distribution IS NULL)))
 );
 
+
+COMMENT ON TABLE customlanguagecode IS 'Overrides translation importer''s interpretation of language codes where needed.';
+
+
+COMMENT ON COLUMN customlanguagecode.product IS 'Product for which this custom language code applies (alternative to distribution + source package name).';
+
+
+COMMENT ON COLUMN customlanguagecode.distribution IS 'Distribution in which this custom language code applies (if not a product).';
+
+
+COMMENT ON COLUMN customlanguagecode.sourcepackagename IS 'Source package name to which this custom language code applies; goes with distribution.';
+
+
+COMMENT ON COLUMN customlanguagecode.language_code IS 'Custom language code; need not be for a real language, and typically not for a "useful" language.';
+
+
+COMMENT ON COLUMN customlanguagecode.language IS 'Language to which code really refers in this context, or NULL if files with this code are to be rejected.';
+
+
 CREATE SEQUENCE customlanguagecode_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE customlanguagecode_id_seq OWNED BY customlanguagecode.id;
 
+
 CREATE TABLE cve (
     id integer NOT NULL,
     sequence text NOT NULL,
@@ -1713,14 +7938,30 @@
     CONSTRAINT valid_cve_ref CHECK (valid_cve(sequence))
 );
 
+
+COMMENT ON TABLE cve IS 'A CVE Entry. The formal database of CVE entries is available at http://cve.mitre.org/ and we sync that database into Launchpad on a regular basis.';
+
+
+COMMENT ON COLUMN cve.sequence IS 'The official CVE entry number. It takes the form XXXX-XXXX where the first four digits are a year indicator, like 2004, and the latter four are the sequence number of the vulnerability in that year.';
+
+
+COMMENT ON COLUMN cve.status IS 'The current status of the CVE. The values are documented in dbschema.CVEState, and are Entry, Candidate, and Deprecated.';
+
+
+COMMENT ON COLUMN cve.datemodified IS 'The last time this CVE entry changed in some way - including addition or modification of references.';
+
+
 CREATE SEQUENCE cve_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE cve_id_seq OWNED BY cve.id;
 
+
 CREATE TABLE cvereference (
     id integer NOT NULL,
     cve integer NOT NULL,
@@ -1730,19 +7971,64 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE cvereference IS 'A reference in the CVE system that shows what outside tracking numbers are associated with the CVE. These are tracked in the CVE database and extracted from the daily XML dump that we fetch.';
+
+
+COMMENT ON COLUMN cvereference.source IS 'The SOURCE of the CVE reference. This is a text string, like XF or BUGTRAQ or MSKB. Each string indicates a different kind of reference. The list of known types is documented on the CVE web site. At some future date we might turn this into an enum rather than a text, but for the moment we prefer to keep it fluid and just suck in what CVE gives us. This means that CVE can add new source types without us having to update our code.';
+
+
+COMMENT ON COLUMN cvereference.content IS 'The content of the ref in the CVE database. This is sometimes a comment, sometimes a description, sometimes a bug number... it is not predictable.';
+
+
+COMMENT ON COLUMN cvereference.url IS 'The URL to this reference out there on the web, if it was present in the CVE database.';
+
+
 CREATE SEQUENCE cvereference_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE cvereference_id_seq OWNED BY cvereference.id;
 
+
 CREATE TABLE databasecpustats (
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     username text NOT NULL,
     cpu integer NOT NULL
-);
+)
+WITH (fillfactor=100);
+
+
+COMMENT ON TABLE databasecpustats IS 'Snapshots of CPU utilization per database username.';
+
+
+COMMENT ON COLUMN databasecpustats.cpu IS '% CPU utilization * 100, as reported by ps -o cp';
+
+
+CREATE TABLE databasediskutilization (
+    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
+    namespace text NOT NULL,
+    name text NOT NULL,
+    sub_namespace text,
+    sub_name text,
+    kind character(1) NOT NULL,
+    sort text NOT NULL,
+    table_len bigint NOT NULL,
+    tuple_count bigint NOT NULL,
+    tuple_len bigint NOT NULL,
+    tuple_percent double precision NOT NULL,
+    dead_tuple_count bigint NOT NULL,
+    dead_tuple_len bigint NOT NULL,
+    dead_tuple_percent double precision NOT NULL,
+    free_space bigint NOT NULL,
+    free_percent double precision NOT NULL
+)
+WITH (fillfactor=100);
+
 
 CREATE TABLE databasereplicationlag (
     node integer NOT NULL,
@@ -1750,6 +8036,19 @@
     updated timestamp without time zone DEFAULT timezone('UTC'::text, now())
 );
 
+
+COMMENT ON TABLE databasereplicationlag IS 'A cached snapshot of database replication lag between our master Slony node and its slaves.';
+
+
+COMMENT ON COLUMN databasereplicationlag.node IS 'The Slony node number identifying the slave database.';
+
+
+COMMENT ON COLUMN databasereplicationlag.lag IS 'lag time.';
+
+
+COMMENT ON COLUMN databasereplicationlag.updated IS 'When this value was updated.';
+
+
 CREATE TABLE databasetablestats (
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     schemaname name NOT NULL,
@@ -1768,7 +8067,12 @@
     last_autovacuum timestamp with time zone,
     last_analyze timestamp with time zone,
     last_autoanalyze timestamp with time zone
-);
+)
+WITH (fillfactor=100);
+
+
+COMMENT ON TABLE databasetablestats IS 'Snapshots of pg_stat_user_tables to let us calculate arbitrary deltas';
+
 
 CREATE TABLE diff (
     id integer NOT NULL,
@@ -1779,14 +8083,36 @@
     removed_lines_count integer
 );
 
+
+COMMENT ON TABLE diff IS 'Information common to static or preview diffs';
+
+
+COMMENT ON COLUMN diff.diff_text IS 'The library copy of the fulltext of the diff';
+
+
+COMMENT ON COLUMN diff.diff_lines_count IS 'The number of lines in the diff';
+
+
+COMMENT ON COLUMN diff.diffstat IS 'Statistics about the diff';
+
+
+COMMENT ON COLUMN diff.added_lines_count IS 'The number of lines added in the diff.';
+
+
+COMMENT ON COLUMN diff.removed_lines_count IS 'The number of lines removed in the diff';
+
+
 CREATE SEQUENCE diff_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE diff_id_seq OWNED BY diff.id;
 
+
 CREATE TABLE distribution (
     id integer NOT NULL,
     name text NOT NULL,
@@ -1794,7 +8120,6 @@
     description text NOT NULL,
     domainname text NOT NULL,
     owner integer NOT NULL,
-    lucilleconfig text,
     displayname text NOT NULL,
     summary text NOT NULL,
     members integer NOT NULL,
@@ -1826,32 +8151,156 @@
     answers_usage integer DEFAULT 10 NOT NULL,
     blueprints_usage integer DEFAULT 10 NOT NULL,
     translations_usage integer DEFAULT 10 NOT NULL,
+    registrant integer NOT NULL,
+    package_derivatives_email text,
     CONSTRAINT only_launchpad_has_expiration CHECK (((enable_bug_expiration IS FALSE) OR (official_malone IS TRUE))),
     CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
+
+COMMENT ON TABLE distribution IS 'Distribution: A soyuz distribution. A distribution is a collection of DistroSeries. Distributions often group together policy and may be referred to by a name such as "Ubuntu" or "Debian"';
+
+
+COMMENT ON COLUMN distribution.name IS 'The unique name of the distribution as a short lowercase name suitable for use in a URL.';
+
+
+COMMENT ON COLUMN distribution.title IS 'The title of the distribution. More a "display name" as it were. E.g. "Ubuntu" or "Debian GNU/Linux"';
+
+
+COMMENT ON COLUMN distribution.description IS 'A description of the distribution. More detailed than the title, this column may also contain information about the project this distribution is run by.';
+
+
+COMMENT ON COLUMN distribution.domainname IS 'The domain name of the distribution. This may be used both for linking to the distribution and for context-related stuff.';
+
+
+COMMENT ON COLUMN distribution.owner IS 'The person in launchpad who is in ultimate-charge of this distribution within launchpad.';
+
+
+COMMENT ON COLUMN distribution.displayname IS 'A short, well-capitalised
+name for this distribution that is not required to be unique but in almost
+all cases would be so.';
+
+
+COMMENT ON COLUMN distribution.summary IS 'A single paragraph that
+summarises the highlights of this distribution. It should be no longer than
+240 characters, although this is not enforced in the database.';
+
+
+COMMENT ON COLUMN distribution.members IS 'Person or team with upload and commit priviledges relating to this distribution. Other rights may be assigned to this role in the future.';
+
+
+COMMENT ON COLUMN distribution.translationgroup IS 'The translation group that is responsible for all translation work in this distribution.';
+
+
+COMMENT ON COLUMN distribution.translationpermission IS 'The level of openness of this distribution''s translation process. The enum lists different approaches to translation, from the very open (anybody can edit any translation in any language) to the completely closed (only designated translators can make any changes at all).';
+
+
+COMMENT ON COLUMN distribution.bug_supervisor IS 'Person who is responsible for managing bugs on this distribution.';
+
+
+COMMENT ON COLUMN distribution.official_malone IS 'Whether or not this distribution uses Malone for an official bug tracker.';
+
+
+COMMENT ON COLUMN distribution.official_rosetta IS 'Whether or not this distribution uses Rosetta for its official translation team and coordination.';
+
+
+COMMENT ON COLUMN distribution.security_contact IS 'The person or team who handles security-related issues in the distribution.';
+
+
+COMMENT ON COLUMN distribution.driver IS 'The team or person responsible for approving goals for each release in the distribution. This should usually be a very small team because the Distribution driver can approve items for backporting to past releases as well as the current release under development. Each distroseries has its own driver too, so you can have the small superset in the Distribution driver, and then specific teams per distroseries for backporting, for example, or for the current release management team on the current development focus release.';
+
+
+COMMENT ON COLUMN distribution.translation_focus IS 'The DistroSeries that should get the translation effort focus.';
+
+
+COMMENT ON COLUMN distribution.mirror_admin IS 'Person or team with privileges to mark a mirror as official.';
+
+
+COMMENT ON COLUMN distribution.upload_admin IS 'Person foreign key which have access to modify the queue ui. If NULL, we fall back to launchpad admin members';
+
+
+COMMENT ON COLUMN distribution.upload_sender IS 'The email address (and name) of the default sender used by the upload processor. If NULL, we fall back to the default sender in the launchpad config.';
+
+
+COMMENT ON COLUMN distribution.homepage_content IS 'A home page for this distribution in the Launchpad.';
+
+
+COMMENT ON COLUMN distribution.icon IS 'The library file alias to a small image to be used as an icon whenever we are referring to a distribution.';
+
+
+COMMENT ON COLUMN distribution.mugshot IS 'The library file alias of a mugshot image to display as the branding of a distribution, on its home page.';
+
+
+COMMENT ON COLUMN distribution.logo IS 'The library file alias of a smaller version of this distributions''s mugshot.';
+
+
+COMMENT ON COLUMN distribution.official_answers IS 'Whether or not this product upstream uses Answers officialy.';
+
+
+COMMENT ON COLUMN distribution.language_pack_admin IS 'The Person or Team that handle language packs for the distro release.';
+
+
+COMMENT ON COLUMN distribution.enable_bug_expiration IS 'Indicates whether automatic bug expiration is enabled.';
+
+
+COMMENT ON COLUMN distribution.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on this distribution.';
+
+
+COMMENT ON COLUMN distribution.reviewer_whiteboard IS 'A whiteboard for Launchpad admins, registry experts and the project owners to capture the state of current issues with the project.';
+
+
+COMMENT ON COLUMN distribution.max_bug_heat IS 'The highest heat value across bugs for this distribution.';
+
+
+COMMENT ON COLUMN distribution.bug_reported_acknowledgement IS 'A message of acknowledgement to display to a bug reporter after they''ve reported a new bug.';
+
+
 CREATE SEQUENCE distribution_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE distribution_id_seq OWNED BY distribution.id;
 
-CREATE TABLE distributionbounty (
+
+CREATE TABLE distributionjob (
     id integer NOT NULL,
-    bounty integer NOT NULL,
+    job integer NOT NULL,
     distribution integer NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
+    distroseries integer,
+    job_type integer NOT NULL,
+    json_data text
 );
 
-CREATE SEQUENCE distributionbounty_id_seq
+
+COMMENT ON TABLE distributionjob IS 'Contains references to jobs to be run on distributions.';
+
+
+COMMENT ON COLUMN distributionjob.distribution IS 'The distribution to be acted on.';
+
+
+COMMENT ON COLUMN distributionjob.distroseries IS 'The distroseries to be acted on.';
+
+
+COMMENT ON COLUMN distributionjob.job_type IS 'The type of job';
+
+
+COMMENT ON COLUMN distributionjob.json_data IS 'A JSON struct containing data for the job.';
+
+
+CREATE SEQUENCE distributionjob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
-ALTER SEQUENCE distributionbounty_id_seq OWNED BY distributionbounty.id;
+
+ALTER SEQUENCE distributionjob_id_seq OWNED BY distributionjob.id;
+
 
 CREATE TABLE distributionmirror (
     id integer NOT NULL,
@@ -1881,14 +8330,78 @@
     CONSTRAINT valid_rsync_base_url CHECK (valid_absolute_url(rsync_base_url))
 );
 
+
+COMMENT ON TABLE distributionmirror IS 'A mirror of a given distribution.';
+
+
+COMMENT ON COLUMN distributionmirror.distribution IS 'The distribution to which the mirror refers to.';
+
+
+COMMENT ON COLUMN distributionmirror.name IS 'The unique name of the mirror.';
+
+
+COMMENT ON COLUMN distributionmirror.http_base_url IS 'The HTTP URL used to access the mirror.';
+
+
+COMMENT ON COLUMN distributionmirror.ftp_base_url IS 'The FTP URL used to access the mirror.';
+
+
+COMMENT ON COLUMN distributionmirror.rsync_base_url IS 'The Rsync URL used to access the mirror.';
+
+
+COMMENT ON COLUMN distributionmirror.displayname IS 'The displayname of the mirror.';
+
+
+COMMENT ON COLUMN distributionmirror.description IS 'A description of the mirror.';
+
+
+COMMENT ON COLUMN distributionmirror.owner IS 'The owner of the mirror.';
+
+
+COMMENT ON COLUMN distributionmirror.speed IS 'The speed of the mirror''s Internet link.';
+
+
+COMMENT ON COLUMN distributionmirror.country IS 'The country where the mirror is located.';
+
+
+COMMENT ON COLUMN distributionmirror.content IS 'The content that is mirrored.';
+
+
+COMMENT ON COLUMN distributionmirror.official_candidate IS 'Is the mirror a candidate for becoming an official mirror?';
+
+
+COMMENT ON COLUMN distributionmirror.enabled IS 'Is this mirror enabled?';
+
+
+COMMENT ON COLUMN distributionmirror.date_created IS 'The date and time the mirror was created.';
+
+
+COMMENT ON COLUMN distributionmirror.whiteboard IS 'Notes on the current status of the mirror';
+
+
+COMMENT ON COLUMN distributionmirror.status IS 'This mirror''s status.';
+
+
+COMMENT ON COLUMN distributionmirror.date_reviewed IS 'The date and time the mirror was reviewed.';
+
+
+COMMENT ON COLUMN distributionmirror.reviewer IS 'The person who reviewed the mirror.';
+
+
+COMMENT ON COLUMN distributionmirror.country_dns_mirror IS 'Is the mirror a country DNS mirror?';
+
+
 CREATE SEQUENCE distributionmirror_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE distributionmirror_id_seq OWNED BY distributionmirror.id;
 
+
 CREATE TABLE distributionsourcepackage (
     id integer NOT NULL,
     distribution integer NOT NULL,
@@ -1899,17 +8412,49 @@
     total_bug_heat integer,
     bug_count integer,
     po_message_count integer,
-    is_upstream_link_allowed boolean DEFAULT true NOT NULL
+    is_upstream_link_allowed boolean DEFAULT true NOT NULL,
+    enable_bugfiling_duplicate_search boolean DEFAULT true NOT NULL
 );
 
+
+COMMENT ON TABLE distributionsourcepackage IS 'Representing a sourcepackage in a distribution across all distribution series.';
+
+
+COMMENT ON COLUMN distributionsourcepackage.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on a particular a source package in a distribution.';
+
+
+COMMENT ON COLUMN distributionsourcepackage.max_bug_heat IS 'The highest heat value across bugs for this source package. NULL means it has not yet been calculated.';
+
+
+COMMENT ON COLUMN distributionsourcepackage.bug_reported_acknowledgement IS 'A message of acknowledgement to display to a bug reporter after they''ve reported a new bug.';
+
+
+COMMENT ON COLUMN distributionsourcepackage.total_bug_heat IS 'Sum of bug heat matching the package distribution and sourcepackagename. NULL means it has not yet been calculated.';
+
+
+COMMENT ON COLUMN distributionsourcepackage.bug_count IS 'Number of bugs matching the package distribution and sourcepackagename. NULL means it has not yet been calculated.';
+
+
+COMMENT ON COLUMN distributionsourcepackage.po_message_count IS 'Number of translations matching the package distribution and sourcepackagename. NULL means it has not yet been calculated.';
+
+
+COMMENT ON COLUMN distributionsourcepackage.is_upstream_link_allowed IS 'Whether an upstream link may be added if it does not already exist.';
+
+
+COMMENT ON COLUMN distributionsourcepackage.enable_bugfiling_duplicate_search IS 'Enable/disable a search for posiible duplicates when a bug is filed.';
+
+
 CREATE SEQUENCE distributionsourcepackage_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE distributionsourcepackage_id_seq OWNED BY distributionsourcepackage.id;
 
+
 CREATE TABLE distributionsourcepackagecache (
     id integer NOT NULL,
     distribution integer NOT NULL,
@@ -1923,46 +8468,153 @@
     archive integer NOT NULL
 );
 
+
+COMMENT ON TABLE distributionsourcepackagecache IS 'A cache of the text associated with binary and source packages in the distribution. This table allows for fast queries to find a source packagename that matches a given text.';
+
+
+COMMENT ON COLUMN distributionsourcepackagecache.distribution IS 'The distribution in which we are checking.';
+
+
+COMMENT ON COLUMN distributionsourcepackagecache.sourcepackagename IS 'The source package name for which we are caching details.';
+
+
+COMMENT ON COLUMN distributionsourcepackagecache.name IS 'The source package name itself. This is just a copy of the value of sourcepackagename.name. We have it here so it can be part of the full text index.';
+
+
+COMMENT ON COLUMN distributionsourcepackagecache.binpkgnames IS 'The binary package names of binary packages generated from these source packages across all architectures.';
+
+
+COMMENT ON COLUMN distributionsourcepackagecache.binpkgsummaries IS 'The aggregated summaries of all the binary packages generated from these source packages in this distribution.';
+
+
+COMMENT ON COLUMN distributionsourcepackagecache.binpkgdescriptions IS 'The aggregated description of all the binary packages generated from these source packages in this distribution.';
+
+
+COMMENT ON COLUMN distributionsourcepackagecache.changelog IS 'A concatenation of the source package release changelogs for this source package, where the status is not REMOVED.';
+
+
+COMMENT ON COLUMN distributionsourcepackagecache.archive IS 'The archive where the source is published.';
+
+
 CREATE SEQUENCE distributionsourcepackagecache_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE distributionsourcepackagecache_id_seq OWNED BY distributionsourcepackagecache.id;
 
+
 CREATE SEQUENCE distroarchseries_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE distroarchseries_id_seq OWNED BY distroarchseries.id;
 
-CREATE TABLE distrocomponentuploader (
-    id integer NOT NULL,
-    distribution integer NOT NULL,
-    component integer NOT NULL,
-    uploader integer NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
-);
-
-CREATE SEQUENCE distrocomponentuploader_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE distrocomponentuploader_id_seq OWNED BY distrocomponentuploader.id;
 
 CREATE SEQUENCE distroseries_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE distroseries_id_seq OWNED BY distroseries.id;
 
+
+CREATE TABLE distroseriesdifference (
+    id integer NOT NULL,
+    derived_series integer NOT NULL,
+    source_package_name integer NOT NULL,
+    package_diff integer,
+    status integer NOT NULL,
+    difference_type integer NOT NULL,
+    parent_package_diff integer,
+    source_version debversion,
+    parent_source_version debversion,
+    base_version debversion,
+    parent_series integer NOT NULL,
+    CONSTRAINT valid_base_version CHECK (valid_debian_version((base_version)::text)),
+    CONSTRAINT valid_parent_source_version CHECK (valid_debian_version((parent_source_version)::text)),
+    CONSTRAINT valid_source_version CHECK (valid_debian_version((source_version)::text))
+);
+
+
+COMMENT ON TABLE distroseriesdifference IS 'A difference of versions for a package in a derived distroseries and its parent distroseries.';
+
+
+COMMENT ON COLUMN distroseriesdifference.derived_series IS 'The derived distroseries with the difference from its parent.';
+
+
+COMMENT ON COLUMN distroseriesdifference.source_package_name IS 'The name of the source package which is different in the two series.';
+
+
+COMMENT ON COLUMN distroseriesdifference.package_diff IS 'The most recent package diff that was created for the base version to derived version.';
+
+
+COMMENT ON COLUMN distroseriesdifference.status IS 'A distroseries difference can be needing attention, ignored or resolved.';
+
+
+COMMENT ON COLUMN distroseriesdifference.difference_type IS 'The type of difference that this record represents - a package unique to the derived series, or missing, or in both.';
+
+
+COMMENT ON COLUMN distroseriesdifference.parent_package_diff IS 'The most recent package diff that was created for the base version to the parent version.';
+
+
+COMMENT ON COLUMN distroseriesdifference.source_version IS 'The version of the package in the derived series.';
+
+
+COMMENT ON COLUMN distroseriesdifference.parent_source_version IS 'The version of the package in the parent series.';
+
+
+COMMENT ON COLUMN distroseriesdifference.base_version IS 'The common base version of the package for the derived and parent series.';
+
+
+CREATE SEQUENCE distroseriesdifference_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE distroseriesdifference_id_seq OWNED BY distroseriesdifference.id;
+
+
+CREATE TABLE distroseriesdifferencemessage (
+    id integer NOT NULL,
+    distro_series_difference integer NOT NULL,
+    message integer NOT NULL
+);
+
+
+COMMENT ON TABLE distroseriesdifferencemessage IS 'A message/comment on a distro series difference.';
+
+
+COMMENT ON COLUMN distroseriesdifferencemessage.distro_series_difference IS 'The distro series difference for this comment.';
+
+
+COMMENT ON COLUMN distroseriesdifferencemessage.message IS 'The comment for the distro series difference.';
+
+
+CREATE SEQUENCE distroseriesdifferencemessage_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE distroseriesdifferencemessage_id_seq OWNED BY distroseriesdifferencemessage.id;
+
+
 CREATE TABLE distroserieslanguage (
     id integer NOT NULL,
     distroseries integer,
@@ -1975,14 +8627,39 @@
     unreviewed_count integer DEFAULT 0 NOT NULL
 );
 
+
+COMMENT ON TABLE distroserieslanguage IS 'A cache of the current translation status of that language across an entire distroseries.';
+
+
+COMMENT ON COLUMN distroserieslanguage.currentcount IS 'As per IRosettaStats.';
+
+
+COMMENT ON COLUMN distroserieslanguage.updatescount IS 'As per IRosettaStats.';
+
+
+COMMENT ON COLUMN distroserieslanguage.rosettacount IS 'As per IRosettaStats.';
+
+
+COMMENT ON COLUMN distroserieslanguage.contributorcount IS 'The total number of contributors to the translation of this distroseries into this language.';
+
+
+COMMENT ON COLUMN distroserieslanguage.dateupdated IS 'The date these statistucs were last updated.';
+
+
+COMMENT ON COLUMN distroserieslanguage.unreviewed_count IS 'As per IRosettaStats.';
+
+
 CREATE SEQUENCE distroserieslanguage_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE distroserieslanguage_id_seq OWNED BY distroserieslanguage.id;
 
+
 CREATE TABLE distroseriespackagecache (
     id integer NOT NULL,
     distroseries integer NOT NULL,
@@ -1996,14 +8673,65 @@
     archive integer NOT NULL
 );
 
+
+COMMENT ON TABLE distroseriespackagecache IS 'A cache of the text associated with binary packages in the distroseries. This table allows for fast queries to find a binary packagename that matches a given text.';
+
+
+COMMENT ON COLUMN distroseriespackagecache.distroseries IS 'The distroseries in which we are checking.';
+
+
+COMMENT ON COLUMN distroseriespackagecache.binarypackagename IS 'The binary package name for which we are caching details.';
+
+
+COMMENT ON COLUMN distroseriespackagecache.name IS 'The binary package name itself. This is just a copy of the value of binarypackagename.name. We have it here so it can be part of the full text index.';
+
+
+COMMENT ON COLUMN distroseriespackagecache.summary IS 'A single summary for one of the binary packages of this name in this distroseries. We could potentially have binary packages in different architectures with the same name and different summaries, so this is a way of collapsing to one arbitrarily-chosen one, for display purposes. The chances of actually having different summaries and descriptions is pretty small. It could happen, though, because of the way package superseding works when a package does not build on a specific architecture.';
+
+
+COMMENT ON COLUMN distroseriespackagecache.summaries IS 'The aggregated summaries of all the binary packages with this name in this distroseries.';
+
+
+COMMENT ON COLUMN distroseriespackagecache.descriptions IS 'The aggregated description of all the binary packages with this name in this distroseries.';
+
+
+COMMENT ON COLUMN distroseriespackagecache.archive IS 'The archive where the binary is published.';
+
+
 CREATE SEQUENCE distroseriespackagecache_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE distroseriespackagecache_id_seq OWNED BY distroseriespackagecache.id;
 
+
+CREATE TABLE distroseriesparent (
+    id integer NOT NULL,
+    derived_series integer NOT NULL,
+    parent_series integer NOT NULL,
+    initialized boolean NOT NULL,
+    is_overlay boolean DEFAULT false NOT NULL,
+    component integer,
+    pocket integer,
+    ordering integer DEFAULT 1 NOT NULL
+);
+
+
+CREATE SEQUENCE distroseriesparent_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE distroseriesparent_id_seq OWNED BY distroseriesparent.id;
+
+
 CREATE TABLE emailaddress (
     id integer NOT NULL,
     email text NOT NULL,
@@ -2014,14 +8742,21 @@
     CONSTRAINT emailaddress__is_linked__chk CHECK (((person IS NOT NULL) OR (account IS NOT NULL)))
 );
 
+
+COMMENT ON COLUMN emailaddress.email IS 'An email address used by a Person. The email address is stored in a casesensitive way, but must be case insensitivly unique.';
+
+
 CREATE SEQUENCE emailaddress_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE emailaddress_id_seq OWNED BY emailaddress.id;
 
+
 CREATE TABLE entitlement (
     id integer NOT NULL,
     person integer,
@@ -2043,17 +8778,73 @@
     CONSTRAINT only_one_target CHECK ((null_count(ARRAY[person, product, project, distribution]) = 3))
 );
 
+
+COMMENT ON TABLE entitlement IS 'Entitlements and usage of privileged features.';
+
+
+COMMENT ON COLUMN entitlement.person IS 'The person to which the entitlements apply.';
+
+
+COMMENT ON COLUMN entitlement.entitlement_type IS 'The type of this entitlement (e.g. private bug).';
+
+
+COMMENT ON COLUMN entitlement.quota IS 'Number of this entitlement allowed.';
+
+
+COMMENT ON COLUMN entitlement.amount_used IS 'Quantity of this entitlement allocation that is used.';
+
+
+COMMENT ON COLUMN entitlement.date_starts IS 'When this entitlement becomes active.';
+
+
+COMMENT ON COLUMN entitlement.date_expires IS 'When this entitlement expires.';
+
+
+COMMENT ON COLUMN entitlement.registrant IS 'The person (admin) who registered this entitlement.  It is NULL if imported directly from an external sales system.';
+
+
+COMMENT ON COLUMN entitlement.date_created IS 'Creation date of entitlement.';
+
+
+COMMENT ON COLUMN entitlement.approved_by IS 'The person who approved this entitlement.  It is NULL if imported directly from an external sales system.';
+
+
+COMMENT ON COLUMN entitlement.date_approved IS 'Approval date of entitlement.  It is NULL if imported directly from an external sales system.';
+
+
+COMMENT ON COLUMN entitlement.state IS 'The state (REQUESTED, ACTIVE, INACTIVE) of the entitlement.';
+
+
+COMMENT ON COLUMN entitlement.whiteboard IS 'A place for administrator notes.';
+
+
+COMMENT ON COLUMN entitlement.is_dirty IS 'This entitlement has been modified and the state needst to be updated on the external system.';
+
+
+COMMENT ON COLUMN entitlement.distribution IS 'The distribution to which this entitlement applies.';
+
+
+COMMENT ON COLUMN entitlement.product IS 'The product to which this entitlement applies.';
+
+
+COMMENT ON COLUMN entitlement.project IS 'The project to which this entitlement applies.';
+
+
 CREATE SEQUENCE entitlement_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE entitlement_id_seq OWNED BY entitlement.id;
 
+
 CREATE VIEW exclusivelocks AS
     SELECT alllocks.procpid, alllocks.usename, alllocks.age, alllocks.relname, alllocks.mode, alllocks.granted, alllocks.current_query FROM alllocks WHERE (alllocks.mode !~~ '%Share%'::text);
 
+
 CREATE TABLE faq (
     id integer NOT NULL,
     title text NOT NULL,
@@ -2069,63 +8860,164 @@
     CONSTRAINT product_or_distro CHECK (((product IS NULL) <> (distribution IS NULL)))
 );
 
+
+COMMENT ON TABLE faq IS 'A technical document containing the answer to a common question.';
+
+
+COMMENT ON COLUMN faq.id IS 'The FAQ document sequence number.';
+
+
+COMMENT ON COLUMN faq.title IS 'The document title.';
+
+
+COMMENT ON COLUMN faq.tags IS 'White-space separated list of tags.';
+
+
+COMMENT ON COLUMN faq.content IS 'The content of FAQ. It can also contain a short summary and a link.';
+
+
+COMMENT ON COLUMN faq.product IS 'The product to which this document is
+related. Either "product" or "distribution" must be set.';
+
+
+COMMENT ON COLUMN faq.distribution IS 'The distribution to which this document
+is related. Either "product" or "distribution" must be set.';
+
+
+COMMENT ON COLUMN faq.owner IS 'The person who created the document.';
+
+
+COMMENT ON COLUMN faq.date_created IS 'The datetime when the document was created.';
+
+
+COMMENT ON COLUMN faq.last_updated_by IS 'The person who last modified the document.';
+
+
+COMMENT ON COLUMN faq.date_last_updated IS 'The datetime when the document was last modified.';
+
+
 CREATE SEQUENCE faq_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE faq_id_seq OWNED BY faq.id;
 
+
 CREATE TABLE featuredproject (
     id integer NOT NULL,
     pillar_name integer NOT NULL
 );
 
+
+COMMENT ON TABLE featuredproject IS 'A list of featured projects. This table is really just a list of pillarname IDs, if a project''s pillar name is in this list then it is a featured project and will be listed on the Launchpad home page.';
+
+
+COMMENT ON COLUMN featuredproject.pillar_name IS 'A reference to PillarName.id';
+
+
 CREATE SEQUENCE featuredproject_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE featuredproject_id_seq OWNED BY featuredproject.id;
 
+
 CREATE TABLE featureflag (
     scope text NOT NULL,
     priority integer NOT NULL,
     flag text NOT NULL,
-    value text,
+    value text NOT NULL,
     date_modified timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE featureflag IS 'Configuration that varies by the active scope and that 
+can be changed without restarting Launchpad
+<https://dev.launchpad.net/LEP/FeatureFlags>';
+
+
+COMMENT ON COLUMN featureflag.scope IS 'Scope in which this setting is active';
+
+
+COMMENT ON COLUMN featureflag.priority IS 'Higher priority flags override lower';
+
+
+COMMENT ON COLUMN featureflag.flag IS 'Name of the flag being controlled';
+
+
+CREATE TABLE featureflagchangelogentry (
+    id integer NOT NULL,
+    date_changed timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
+    diff text NOT NULL,
+    comment text NOT NULL,
+    person integer NOT NULL
+);
+
+
+CREATE SEQUENCE featureflagchangelogentry_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE featureflagchangelogentry_id_seq OWNED BY featureflagchangelogentry.id;
+
+
 CREATE TABLE flatpackagesetinclusion (
     id integer NOT NULL,
     parent integer NOT NULL,
     child integer NOT NULL
 );
 
+
+COMMENT ON TABLE flatpackagesetinclusion IS 'In order to facilitate the querying of set-subset relationships an expanded or flattened representation of the set-subset hierarchy is provided by this table.';
+
+
+COMMENT ON COLUMN flatpackagesetinclusion.parent IS 'The package set that is (directly or indirectly) including a subset.';
+
+
+COMMENT ON COLUMN flatpackagesetinclusion.child IS 'The package set that is being included as a subset.';
+
+
 CREATE SEQUENCE flatpackagesetinclusion_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE flatpackagesetinclusion_id_seq OWNED BY flatpackagesetinclusion.id;
 
+
 CREATE TABLE fticache (
     id integer NOT NULL,
     tablename text NOT NULL,
     columns text NOT NULL
 );
 
+
 CREATE SEQUENCE fticache_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE fticache_id_seq OWNED BY fticache.id;
 
+
 CREATE TABLE gpgkey (
     id integer NOT NULL,
     owner integer NOT NULL,
@@ -2140,14 +9032,39 @@
     CONSTRAINT valid_keyid CHECK (valid_keyid(keyid))
 );
 
+
+COMMENT ON TABLE gpgkey IS 'A GPG key belonging to a Person';
+
+
+COMMENT ON COLUMN gpgkey.keyid IS 'The 8 character GPG key id, uppercase and no whitespace';
+
+
+COMMENT ON COLUMN gpgkey.fingerprint IS 'The 40 character GPG fingerprint, uppercase and no whitespace';
+
+
+COMMENT ON COLUMN gpgkey.active IS 'True if this key is active for use in Launchpad context, false could be deactivated by user or revoked in the global key ring.';
+
+
+COMMENT ON COLUMN gpgkey.algorithm IS 'The algorithm used to generate this key. Valid values defined in dbschema.GPGKeyAlgorithms';
+
+
+COMMENT ON COLUMN gpgkey.keysize IS 'Size of the key in bits, as reported by GPG. We may refuse to deal with keysizes < 768 bits in the future.';
+
+
+COMMENT ON COLUMN gpgkey.can_encrypt IS 'Whether the key has been validated for use in encryption (as opposed to just signing)';
+
+
 CREATE SEQUENCE gpgkey_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE gpgkey_id_seq OWNED BY gpgkey.id;
 
+
 CREATE TABLE hwdevice (
     id integer NOT NULL,
     bus_vendor_id integer NOT NULL,
@@ -2157,14 +9074,36 @@
     submissions integer NOT NULL
 );
 
+
+COMMENT ON TABLE hwdevice IS 'Basic information on devices.';
+
+
+COMMENT ON COLUMN hwdevice.bus_vendor_id IS 'A reference to a HWVendorID record.';
+
+
+COMMENT ON COLUMN hwdevice.bus_product_id IS 'The bus product ID of a device';
+
+
+COMMENT ON COLUMN hwdevice.variant IS 'An optional additional description for a device that shares its vendor and product ID with another, technically different, device.';
+
+
+COMMENT ON COLUMN hwdevice.name IS 'The human readable product name of the device.';
+
+
+COMMENT ON COLUMN hwdevice.submissions IS 'The number of submissions that contain this device.';
+
+
 CREATE SEQUENCE hwdevice_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwdevice_id_seq OWNED BY hwdevice.id;
 
+
 CREATE TABLE hwdeviceclass (
     id integer NOT NULL,
     device integer NOT NULL,
@@ -2172,28 +9111,57 @@
     sub_class integer
 );
 
+
+COMMENT ON TABLE hwdeviceclass IS 'Capabilities of a device.';
+
+
+COMMENT ON COLUMN hwdeviceclass.device IS 'A reference to a device.';
+
+
+COMMENT ON COLUMN hwdeviceclass.main_class IS 'The main class of a device. Legal values are defined by the HWMainClass enumeration.';
+
+
+COMMENT ON COLUMN hwdeviceclass.sub_class IS 'The sub-class of a device. Legal values are defined by the HWSubClass enumeration.';
+
+
 CREATE SEQUENCE hwdeviceclass_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwdeviceclass_id_seq OWNED BY hwdeviceclass.id;
 
+
 CREATE TABLE hwdevicedriverlink (
     id integer NOT NULL,
     device integer NOT NULL,
     driver integer
 );
 
+
+COMMENT ON TABLE hwdevicedriverlink IS 'Combinations of devices and drivers mentioned in submissions.';
+
+
+COMMENT ON COLUMN hwdevicedriverlink.device IS 'The device controlled by the driver.';
+
+
+COMMENT ON COLUMN hwdevicedriverlink.driver IS 'The driver controlling the device.';
+
+
 CREATE SEQUENCE hwdevicedriverlink_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwdevicedriverlink_id_seq OWNED BY hwdevicedriverlink.id;
 
+
 CREATE TABLE hwdevicenamevariant (
     id integer NOT NULL,
     vendor_name integer NOT NULL,
@@ -2202,14 +9170,33 @@
     submissions integer NOT NULL
 );
 
+
+COMMENT ON TABLE hwdevicenamevariant IS 'Alternative vendor and product names of devices.';
+
+
+COMMENT ON COLUMN hwdevicenamevariant.vendor_name IS 'The alternative vendor name.';
+
+
+COMMENT ON COLUMN hwdevicenamevariant.product_name IS 'The alternative product name.';
+
+
+COMMENT ON COLUMN hwdevicenamevariant.device IS 'The device named by this alternative vendor and product names.';
+
+
+COMMENT ON COLUMN hwdevicenamevariant.submissions IS 'The number of submissions containing this alternative vendor and product name.';
+
+
 CREATE SEQUENCE hwdevicenamevariant_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwdevicenamevariant_id_seq OWNED BY hwdevicenamevariant.id;
 
+
 CREATE TABLE hwdmihandle (
     id integer NOT NULL,
     handle integer NOT NULL,
@@ -2217,14 +9204,27 @@
     submission integer
 );
 
+
+COMMENT ON TABLE hwdmihandle IS 'A DMI Handle appearing in the DMI data of a submission.';
+
+
+COMMENT ON COLUMN hwdmihandle.handle IS 'The ID of the handle.';
+
+
+COMMENT ON COLUMN hwdmihandle.type IS 'The type of the handle.';
+
+
 CREATE SEQUENCE hwdmihandle_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwdmihandle_id_seq OWNED BY hwdmihandle.id;
 
+
 CREATE TABLE hwdmivalue (
     id integer NOT NULL,
     key text,
@@ -2232,14 +9232,30 @@
     handle integer NOT NULL
 );
 
+
+COMMENT ON TABLE hwdmivalue IS 'Key/value pairs of DMI data of a handle.';
+
+
+COMMENT ON COLUMN hwdmivalue.key IS 'The key.';
+
+
+COMMENT ON COLUMN hwdmivalue.value IS 'The value';
+
+
+COMMENT ON COLUMN hwdmivalue.handle IS 'The handle to which this key/value pair belongs.';
+
+
 CREATE SEQUENCE hwdmivalue_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwdmivalue_id_seq OWNED BY hwdmivalue.id;
 
+
 CREATE TABLE hwdriver (
     id integer NOT NULL,
     package_name text,
@@ -2247,20 +9263,47 @@
     license integer
 );
 
+
+COMMENT ON TABLE hwdriver IS 'Information about a driver for a device';
+
+
+COMMENT ON COLUMN hwdriver.package_name IS 'The Debian package name a driver is a part of';
+
+
+COMMENT ON COLUMN hwdriver.name IS 'The name of a driver.';
+
+
 CREATE SEQUENCE hwdriver_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwdriver_id_seq OWNED BY hwdriver.id;
 
+
 CREATE VIEW hwdrivernames AS
     SELECT DISTINCT ON (hwdriver.name) hwdriver.id, hwdriver.name FROM hwdriver ORDER BY hwdriver.name, hwdriver.id;
 
+
+COMMENT ON VIEW hwdrivernames IS 'A view returning the distinct driver names stored in HWDriver.';
+
+
+COMMENT ON COLUMN hwdrivernames.name IS 'The name of a driver.';
+
+
 CREATE VIEW hwdriverpackagenames AS
     SELECT DISTINCT ON (hwdriver.package_name) hwdriver.id, hwdriver.package_name FROM hwdriver ORDER BY hwdriver.package_name, hwdriver.id;
 
+
+COMMENT ON VIEW hwdriverpackagenames IS 'A view returning the distinct Debian package names stored in HWDriver.';
+
+
+COMMENT ON COLUMN hwdriverpackagenames.package_name IS 'The Debian package name a driver is a part of.';
+
+
 CREATE TABLE hwsubmission (
     id integer NOT NULL,
     date_created timestamp without time zone NOT NULL,
@@ -2277,28 +9320,78 @@
     raw_emailaddress text
 );
 
+
+COMMENT ON TABLE hwsubmission IS 'Raw HWDB submission data';
+
+
+COMMENT ON COLUMN hwsubmission.date_created IS 'Date and time of the submission (generated by the client).';
+
+
+COMMENT ON COLUMN hwsubmission.date_submitted IS 'Date and time of the submission (generated by the server).';
+
+
+COMMENT ON COLUMN hwsubmission.format IS 'The format version of the submitted data, as given by the HWDB client. See HWSubmissionFormat for valid values.';
+
+
+COMMENT ON COLUMN hwsubmission.status IS 'The status of the submission. See HWSubmissionProcessingStatus for valid values.';
+
+
+COMMENT ON COLUMN hwsubmission.private IS 'If false, the submitter allows public access to the data. If true, the data may be used only for statistical purposes.';
+
+
+COMMENT ON COLUMN hwsubmission.contactable IS 'If True, the submitter agrees to be contacted by upstream developers and package maintainers for tests etc.';
+
+
+COMMENT ON COLUMN hwsubmission.submission_key IS 'A unique submission ID.';
+
+
+COMMENT ON COLUMN hwsubmission.owner IS 'A reference to the Person table: The owner/submitter of the data.';
+
+
+COMMENT ON COLUMN hwsubmission.distroarchseries IS 'A reference to the distroarchseries of the submission. This value is null, if the submitted values for distribution, distroseries and architecture do not match an existing entry in the Distroarchseries table.';
+
+
+COMMENT ON COLUMN hwsubmission.raw_submission IS 'A reference to a row of LibraryFileAlias. The library file contains the raw submission data.';
+
+
+COMMENT ON COLUMN hwsubmission.system_fingerprint IS 'A reference to an entry of the HWDBSystemFingerPrint table. This table stores the system name as returned by HAL (system.vendor, system.product)';
+
+
+COMMENT ON COLUMN hwsubmission.raw_emailaddress IS 'The email address of the submitter.';
+
+
 CREATE SEQUENCE hwsubmission_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwsubmission_id_seq OWNED BY hwsubmission.id;
 
+
 CREATE TABLE hwsubmissionbug (
     id integer NOT NULL,
     submission integer NOT NULL,
     bug integer NOT NULL
 );
 
+
+COMMENT ON TABLE hwsubmissionbug IS 'Link bugs to HWDB submissions';
+
+
 CREATE SEQUENCE hwsubmissionbug_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwsubmissionbug_id_seq OWNED BY hwsubmissionbug.id;
 
+
 CREATE TABLE hwsubmissiondevice (
     id integer NOT NULL,
     device_driver_link integer NOT NULL,
@@ -2307,27 +9400,56 @@
     hal_device_id integer NOT NULL
 );
 
+
+COMMENT ON TABLE hwsubmissiondevice IS 'Links between devices and submissions.';
+
+
+COMMENT ON COLUMN hwsubmissiondevice.device_driver_link IS 'The combination (device, driver) mentioned in a submission.';
+
+
+COMMENT ON COLUMN hwsubmissiondevice.submission IS 'The submission mentioning this (device, driver) combination.';
+
+
+COMMENT ON COLUMN hwsubmissiondevice.parent IS 'The parent device of this device.';
+
+
+COMMENT ON COLUMN hwsubmissiondevice.hal_device_id IS 'The ID of the HAL node of this device in the submitted data.';
+
+
 CREATE SEQUENCE hwsubmissiondevice_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwsubmissiondevice_id_seq OWNED BY hwsubmissiondevice.id;
 
+
 CREATE TABLE hwsystemfingerprint (
     id integer NOT NULL,
     fingerprint text NOT NULL
 );
 
+
+COMMENT ON TABLE hwsystemfingerprint IS 'A distinct list of "fingerprints" (HAL system.name, system.vendor) from raw submission data';
+
+
+COMMENT ON COLUMN hwsystemfingerprint.fingerprint IS 'The fingerprint';
+
+
 CREATE SEQUENCE hwsystemfingerprint_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwsystemfingerprint_id_seq OWNED BY hwsystemfingerprint.id;
 
+
 CREATE TABLE hwtest (
     id integer NOT NULL,
     namespace text,
@@ -2335,14 +9457,27 @@
     version text NOT NULL
 );
 
+
+COMMENT ON TABLE hwtest IS 'General information about a device test.';
+
+
+COMMENT ON COLUMN hwtest.namespace IS 'The namespace of a test.';
+
+
+COMMENT ON COLUMN hwtest.name IS 'The name of a test.';
+
+
 CREATE SEQUENCE hwtest_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwtest_id_seq OWNED BY hwtest.id;
 
+
 CREATE TABLE hwtestanswer (
     id integer NOT NULL,
     test integer NOT NULL,
@@ -2356,28 +9491,63 @@
     CONSTRAINT hwtestanswer_check CHECK (((((choice IS NULL) AND (unit IS NOT NULL)) AND ((intval IS NULL) <> (floatval IS NULL))) OR ((((choice IS NOT NULL) AND (unit IS NULL)) AND (intval IS NULL)) AND (floatval IS NULL))))
 );
 
+
+COMMENT ON TABLE hwtestanswer IS 'The answer for a test from a submission. This can be either a multiple choice selection or a numerical value. Exactly one of the columns choice, intval, floatval must be non-null.';
+
+
+COMMENT ON COLUMN hwtestanswer.test IS 'The test answered by this answer.';
+
+
+COMMENT ON COLUMN hwtestanswer.choice IS 'The selected value of a multiple choice test.';
+
+
+COMMENT ON COLUMN hwtestanswer.intval IS 'The integer result of a test with a numerical result.';
+
+
+COMMENT ON COLUMN hwtestanswer.floatval IS 'The double precision floating point number result of a test with a numerical result.';
+
+
+COMMENT ON COLUMN hwtestanswer.unit IS 'The physical unit of a test with a numerical result.';
+
+
 CREATE SEQUENCE hwtestanswer_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwtestanswer_id_seq OWNED BY hwtestanswer.id;
 
+
 CREATE TABLE hwtestanswerchoice (
     id integer NOT NULL,
     choice text NOT NULL,
     test integer NOT NULL
 );
 
+
+COMMENT ON TABLE hwtestanswerchoice IS 'Choice values of multiple choice tests/questions.';
+
+
+COMMENT ON COLUMN hwtestanswerchoice.choice IS 'The choice value.';
+
+
+COMMENT ON COLUMN hwtestanswerchoice.test IS 'The test this choice belongs to.';
+
+
 CREATE SEQUENCE hwtestanswerchoice_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwtestanswerchoice_id_seq OWNED BY hwtestanswerchoice.id;
 
+
 CREATE TABLE hwtestanswercount (
     id integer NOT NULL,
     test integer NOT NULL,
@@ -2390,42 +9560,96 @@
     CONSTRAINT hwtestanswercount_check CHECK ((((((choice IS NULL) AND (average IS NOT NULL)) AND (sum_square IS NOT NULL)) AND (unit IS NOT NULL)) OR ((((choice IS NOT NULL) AND (average IS NULL)) AND (sum_square IS NULL)) AND (unit IS NULL))))
 );
 
+
+COMMENT ON TABLE hwtestanswercount IS 'Accumulated results of tests. Either the column choice or the columns average and sum_square must be non-null.';
+
+
+COMMENT ON COLUMN hwtestanswercount.test IS 'The test.';
+
+
+COMMENT ON COLUMN hwtestanswercount.distroarchseries IS 'The distroarchseries for which results are accumulated,';
+
+
+COMMENT ON COLUMN hwtestanswercount.choice IS 'The choice value of a multiple choice test.';
+
+
+COMMENT ON COLUMN hwtestanswercount.average IS 'The average value of the result of a numerical test.';
+
+
+COMMENT ON COLUMN hwtestanswercount.sum_square IS 'The sum of the squares of the results of a numerical test.';
+
+
+COMMENT ON COLUMN hwtestanswercount.unit IS 'The physical unit of a numerical test result.';
+
+
+COMMENT ON COLUMN hwtestanswercount.num_answers IS 'The number of submissions from which the result is accumulated.';
+
+
 CREATE SEQUENCE hwtestanswercount_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwtestanswercount_id_seq OWNED BY hwtestanswercount.id;
 
+
 CREATE TABLE hwtestanswercountdevice (
     id integer NOT NULL,
     answer integer NOT NULL,
     device_driver integer NOT NULL
 );
 
+
+COMMENT ON TABLE hwtestanswercountdevice IS 'Association of accumulated test results and device/driver combinations.';
+
+
+COMMENT ON COLUMN hwtestanswercountdevice.answer IS 'The test answer.';
+
+
+COMMENT ON COLUMN hwtestanswercountdevice.device_driver IS 'The device/driver combination.';
+
+
 CREATE SEQUENCE hwtestanswercountdevice_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwtestanswercountdevice_id_seq OWNED BY hwtestanswercountdevice.id;
 
+
 CREATE TABLE hwtestanswerdevice (
     id integer NOT NULL,
     answer integer NOT NULL,
     device_driver integer NOT NULL
 );
 
+
+COMMENT ON TABLE hwtestanswerdevice IS 'Association of test results and device/driver combinations.';
+
+
+COMMENT ON COLUMN hwtestanswerdevice.answer IS 'The test answer.';
+
+
+COMMENT ON COLUMN hwtestanswerdevice.device_driver IS 'The device/driver combination.';
+
+
 CREATE SEQUENCE hwtestanswerdevice_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwtestanswerdevice_id_seq OWNED BY hwtestanswerdevice.id;
 
+
 CREATE TABLE hwvendorid (
     id integer NOT NULL,
     bus integer NOT NULL,
@@ -2433,27 +9657,85 @@
     vendor_name integer NOT NULL
 );
 
+
+COMMENT ON TABLE hwvendorid IS 'Associates tuples (bus, vendor ID for this bus) with vendor names.';
+
+
+COMMENT ON COLUMN hwvendorid.bus IS 'The bus.';
+
+
+COMMENT ON COLUMN hwvendorid.vendor_id_for_bus IS 'The ID of a vendor for the bus given by column `bus`';
+
+
 CREATE SEQUENCE hwvendorid_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwvendorid_id_seq OWNED BY hwvendorid.id;
 
+
 CREATE TABLE hwvendorname (
     id integer NOT NULL,
     name text NOT NULL
 );
 
+
+COMMENT ON TABLE hwvendorname IS 'A list of hardware vendor names.';
+
+
+COMMENT ON COLUMN hwvendorname.name IS 'The name of a vendor.';
+
+
 CREATE SEQUENCE hwvendorname_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE hwvendorname_id_seq OWNED BY hwvendorname.id;
 
+
+CREATE TABLE incrementaldiff (
+    id integer NOT NULL,
+    diff integer NOT NULL,
+    branch_merge_proposal integer NOT NULL,
+    old_revision integer NOT NULL,
+    new_revision integer NOT NULL
+);
+
+
+COMMENT ON TABLE incrementaldiff IS 'Incremental diffs for merge proposals.';
+
+
+COMMENT ON COLUMN incrementaldiff.diff IS 'The contents of the diff.';
+
+
+COMMENT ON COLUMN incrementaldiff.branch_merge_proposal IS 'The merge proposal the diff is for.';
+
+
+COMMENT ON COLUMN incrementaldiff.old_revision IS 'The revision the diff is from.';
+
+
+COMMENT ON COLUMN incrementaldiff.new_revision IS 'The revision the diff is to.';
+
+
+CREATE SEQUENCE incrementaldiff_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE incrementaldiff_id_seq OWNED BY incrementaldiff.id;
+
+
 CREATE TABLE ircid (
     id integer NOT NULL,
     person integer NOT NULL,
@@ -2461,28 +9743,36 @@
     nickname text NOT NULL
 );
 
+
 CREATE SEQUENCE ircid_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE ircid_id_seq OWNED BY ircid.id;
 
+
 CREATE TABLE jabberid (
     id integer NOT NULL,
     person integer NOT NULL,
     jabberid text NOT NULL
 );
 
+
 CREATE SEQUENCE jabberid_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE jabberid_id_seq OWNED BY jabberid.id;
 
+
 CREATE TABLE job (
     id integer NOT NULL,
     requester integer,
@@ -2501,14 +9791,63 @@
     date_finished timestamp without time zone
 );
 
+
+COMMENT ON TABLE job IS 'Common info about a job.';
+
+
+COMMENT ON COLUMN job.requester IS 'Ther person who requested this job (if applicable).';
+
+
+COMMENT ON COLUMN job.reason IS 'The reason that this job was created (if applicable)';
+
+
+COMMENT ON COLUMN job.status IS 'An enum (JobStatus) indicating the job status, one of: new, in-progress, complete, failed, cancelling, cancelled.';
+
+
+COMMENT ON COLUMN job.progress IS 'The percentage complete.  Can be NULL for some jobs that do not report progress.';
+
+
+COMMENT ON COLUMN job.last_report_seen IS 'The last time the progress was reported.';
+
+
+COMMENT ON COLUMN job.next_report_due IS 'The next time a progress report is expected.';
+
+
+COMMENT ON COLUMN job.attempt_count IS 'The number of times this job has been attempted.';
+
+
+COMMENT ON COLUMN job.max_retries IS 'The maximum number of retries valid for this job.';
+
+
+COMMENT ON COLUMN job.log IS 'If provided, this is the tail of the log file being generated by the running job.';
+
+
+COMMENT ON COLUMN job.scheduled_start IS 'The time when the job should start';
+
+
+COMMENT ON COLUMN job.lease_expires IS 'The time when the lease expires.';
+
+
+COMMENT ON COLUMN job.date_created IS 'The time when the job was created.';
+
+
+COMMENT ON COLUMN job.date_started IS 'If the job has started, the time when the job started.';
+
+
+COMMENT ON COLUMN job.date_finished IS 'If the job has finished, the time when the job finished.';
+
+
 CREATE SEQUENCE job_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE job_id_seq OWNED BY job.id;
 
+
 CREATE TABLE karma (
     id integer NOT NULL,
     datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
@@ -2517,16 +9856,42 @@
     product integer,
     distribution integer,
     sourcepackagename integer
-);
+)
+WITH (fillfactor=100);
+
+
+COMMENT ON TABLE karma IS 'Used to quantify all the ''operations'' a user performs inside the system, which maybe reporting and fixing bugs, uploading packages, end-user support, wiki editting, etc.';
+
+
+COMMENT ON COLUMN karma.datecreated IS 'A timestamp for the assignment of this Karma.';
+
+
+COMMENT ON COLUMN karma.person IS 'The Person for wich this Karma was assigned.';
+
+
+COMMENT ON COLUMN karma.action IS 'A foreign key to the KarmaAction table.';
+
+
+COMMENT ON COLUMN karma.product IS 'The Project to which this Product belongs.  An entry on this table with a non-NULL Project and a NULL Product represents the total karma of the person across all products of that project..';
+
+
+COMMENT ON COLUMN karma.distribution IS 'The Distribution on which a person performed an action that resulted on this karma.';
+
+
+COMMENT ON COLUMN karma.sourcepackagename IS 'The SourcePackageName on which a person performed an action that resulted on this karma.';
+
 
 CREATE SEQUENCE karma_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE karma_id_seq OWNED BY karma.id;
 
+
 CREATE TABLE karmaaction (
     id integer NOT NULL,
     category integer,
@@ -2536,14 +9901,30 @@
     summary text NOT NULL
 );
 
+
+COMMENT ON TABLE karmaaction IS 'Stores all the actions that would give karma to the user which performed it.';
+
+
+COMMENT ON COLUMN karmaaction.category IS 'A dbschema value used to group actions together.';
+
+
+COMMENT ON COLUMN karmaaction.points IS 'The number of points this action is worth of.';
+
+
+COMMENT ON COLUMN karmaaction.name IS 'The unique name of this action.';
+
+
 CREATE SEQUENCE karmaaction_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE karmaaction_id_seq OWNED BY karmaaction.id;
 
+
 CREATE TABLE karmacache (
     id integer NOT NULL,
     person integer NOT NULL,
@@ -2559,14 +9940,30 @@
     CONSTRAINT sourcepackagename_requires_distribution CHECK (((sourcepackagename IS NULL) OR (distribution IS NOT NULL)))
 );
 
+
+COMMENT ON TABLE karmacache IS 'Stores a cached value of a person''s karma points, grouped by the action category and the context where that action was performed.';
+
+
+COMMENT ON COLUMN karmacache.person IS 'The person which performed the actions of this category, and thus got the karma.';
+
+
+COMMENT ON COLUMN karmacache.category IS 'The category of the actions.';
+
+
+COMMENT ON COLUMN karmacache.karmavalue IS 'The karma points of all actions of this category performed by this person on this context (product/distribution).';
+
+
 CREATE SEQUENCE karmacache_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE karmacache_id_seq OWNED BY karmacache.id;
 
+
 CREATE TABLE karmacategory (
     id integer NOT NULL,
     name text NOT NULL,
@@ -2574,28 +9971,41 @@
     summary text NOT NULL
 );
 
+
+COMMENT ON TABLE karmacategory IS 'A category of karma. This allows us to
+present an overall picture of the different areas where a user has been
+active.';
+
+
 CREATE SEQUENCE karmacategory_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE karmacategory_id_seq OWNED BY karmacategory.id;
 
+
 CREATE TABLE karmatotalcache (
     id integer NOT NULL,
     person integer NOT NULL,
     karma_total integer NOT NULL
 );
 
+
 CREATE SEQUENCE karmatotalcache_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE karmatotalcache_id_seq OWNED BY karmatotalcache.id;
 
+
 CREATE TABLE language (
     id integer NOT NULL,
     code text NOT NULL,
@@ -2609,14 +10019,45 @@
     CONSTRAINT valid_language CHECK (((pluralforms IS NULL) = (pluralexpression IS NULL)))
 );
 
+
+COMMENT ON TABLE language IS 'A human language.';
+
+
+COMMENT ON COLUMN language.code IS 'The ISO 639 code for this language';
+
+
+COMMENT ON COLUMN language.englishname IS 'The english name for this language';
+
+
+COMMENT ON COLUMN language.nativename IS 'The name of this language in the language itself';
+
+
+COMMENT ON COLUMN language.pluralforms IS 'The number of plural forms this language has';
+
+
+COMMENT ON COLUMN language.pluralexpression IS 'The plural expression for this language, as used by gettext';
+
+
+COMMENT ON COLUMN language.visible IS 'Whether this language should usually be visible or not';
+
+
+COMMENT ON COLUMN language.direction IS 'The direction that text is written in this language';
+
+
+COMMENT ON COLUMN language.uuid IS 'Mozilla language pack unique ID';
+
+
 CREATE SEQUENCE language_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE language_id_seq OWNED BY language.id;
 
+
 CREATE TABLE languagepack (
     id integer NOT NULL,
     file integer NOT NULL,
@@ -2628,19 +10069,87 @@
     CONSTRAINT valid_updates CHECK ((((type = 2) AND (updates IS NOT NULL)) OR ((type = 1) AND (updates IS NULL))))
 );
 
+
+COMMENT ON TABLE languagepack IS 'Store exported language packs for DistroSeries.';
+
+
+COMMENT ON COLUMN languagepack.file IS 'Librarian file where the language pack is stored.';
+
+
+COMMENT ON COLUMN languagepack.date_exported IS 'When was exported the language pack.';
+
+
+COMMENT ON COLUMN languagepack.date_last_used IS 'When did we stop using the language pack. It''s used to decide whether we can remove it completely from the system. When it''s being used, its value is NULL';
+
+
+COMMENT ON COLUMN languagepack.distroseries IS 'The distribution series from where this language pack was exported.';
+
+
+COMMENT ON COLUMN languagepack.type IS 'Type of language pack. There are two types available, 1: Full export, 2: Update export based on language_pack_that_updates export.';
+
+
+COMMENT ON COLUMN languagepack.updates IS 'The LanguagePack that this one updates.';
+
+
 CREATE SEQUENCE languagepack_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE languagepack_id_seq OWNED BY languagepack.id;
 
+
+CREATE VIEW latestdatabasediskutilization AS
+    SELECT databasediskutilization.date_created, databasediskutilization.namespace, databasediskutilization.name, databasediskutilization.sub_namespace, databasediskutilization.sub_name, databasediskutilization.kind, databasediskutilization.sort, databasediskutilization.table_len, databasediskutilization.tuple_count, databasediskutilization.tuple_len, databasediskutilization.tuple_percent, databasediskutilization.dead_tuple_count, databasediskutilization.dead_tuple_len, databasediskutilization.dead_tuple_percent, databasediskutilization.free_space, databasediskutilization.free_percent FROM databasediskutilization WHERE (databasediskutilization.date_created = (SELECT max(databasediskutilization.date_created) AS max FROM databasediskutilization));
+
+
 CREATE TABLE launchpaddatabaserevision (
     major integer NOT NULL,
     minor integer NOT NULL,
-    patch integer NOT NULL
-);
+    patch integer NOT NULL,
+    start_time timestamp without time zone DEFAULT timezone('UTC'::text, transaction_timestamp()),
+    end_time timestamp without time zone DEFAULT timezone('UTC'::text, statement_timestamp()),
+    branch_nick text,
+    revno integer,
+    revid text
+);
+
+
+COMMENT ON TABLE launchpaddatabaserevision IS 'This table contains a list of the database patches that have been successfully applied to this database.';
+
+
+COMMENT ON COLUMN launchpaddatabaserevision.major IS 'Major number. This is the version of the baseline schema the patch was made agains.';
+
+
+COMMENT ON COLUMN launchpaddatabaserevision.minor IS 'Minor number. Patches made during development each increment the minor number.';
+
+
+COMMENT ON COLUMN launchpaddatabaserevision.patch IS 'The patch number will hopefully always be ''0'', as it exists to support emergency patches made to the production server. eg. If production is running ''4.0.0'' and needs to have a patch applied ASAP, we would create a ''4.0.1'' patch and roll it out. We then may need to refactor all the existing ''4.x.0'' patches.';
+
+
+CREATE TABLE launchpaddatabaseupdatelog (
+    id integer NOT NULL,
+    start_time timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
+    end_time timestamp without time zone,
+    branch_nick text,
+    revno integer,
+    revid text
+);
+
+
+CREATE SEQUENCE launchpaddatabaseupdatelog_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE launchpaddatabaseupdatelog_id_seq OWNED BY launchpaddatabaseupdatelog.id;
+
 
 CREATE TABLE launchpadstatistic (
     id integer NOT NULL,
@@ -2649,22 +10158,32 @@
     dateupdated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL
 );
 
+
+COMMENT ON TABLE launchpadstatistic IS 'A store of system-wide statistics or other integer values, keyed by names. The names are unique and the values can be any integer. Each field has a place to store the timestamp when it was last updated, so it is possible to know how far out of date any given statistic is.';
+
+
 CREATE SEQUENCE launchpadstatistic_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE launchpadstatistic_id_seq OWNED BY launchpadstatistic.id;
 
+
 CREATE SEQUENCE libraryfilealias_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE libraryfilealias_id_seq OWNED BY libraryfilealias.id;
 
+
 CREATE TABLE libraryfilecontent (
     id integer NOT NULL,
     datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
@@ -2672,16 +10191,39 @@
     sha1 character(40) NOT NULL,
     md5 character(32) NOT NULL,
     sha256 character(64)
-);
+)
+WITH (fillfactor=100);
+
+
+COMMENT ON TABLE libraryfilecontent IS 'LibraryFileContent: A librarian file''s contents. The librarian stores files in a safe and transactional way. This table represents the contents of those files within the database.';
+
+
+COMMENT ON COLUMN libraryfilecontent.datecreated IS 'The date on which this librarian file was created';
+
+
+COMMENT ON COLUMN libraryfilecontent.filesize IS 'The size of the file';
+
+
+COMMENT ON COLUMN libraryfilecontent.sha1 IS 'The SHA1 sum of the file''s contents';
+
+
+COMMENT ON COLUMN libraryfilecontent.md5 IS 'The MD5 sum of the file''s contents';
+
+
+COMMENT ON COLUMN libraryfilecontent.sha256 IS 'The SHA256 sum of the file''s contents';
+
 
 CREATE SEQUENCE libraryfilecontent_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE libraryfilecontent_id_seq OWNED BY libraryfilecontent.id;
 
+
 CREATE TABLE libraryfiledownloadcount (
     id integer NOT NULL,
     libraryfilealias integer NOT NULL,
@@ -2690,14 +10232,33 @@
     country integer
 );
 
+
+COMMENT ON TABLE libraryfiledownloadcount IS 'The number of daily downloads for a given LibraryFileAlias.';
+
+
+COMMENT ON COLUMN libraryfiledownloadcount.libraryfilealias IS 'The LibraryFileAlias.';
+
+
+COMMENT ON COLUMN libraryfiledownloadcount.day IS 'The day of the downloads.';
+
+
+COMMENT ON COLUMN libraryfiledownloadcount.count IS 'The number of downloads.';
+
+
+COMMENT ON COLUMN libraryfiledownloadcount.country IS 'The country from where the download requests came from.';
+
+
 CREATE SEQUENCE libraryfiledownloadcount_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE libraryfiledownloadcount_id_seq OWNED BY libraryfiledownloadcount.id;
 
+
 CREATE TABLE logintoken (
     id integer NOT NULL,
     requester integer,
@@ -2712,19 +10273,58 @@
     CONSTRAINT valid_fingerprint CHECK (((fingerprint IS NULL) OR valid_fingerprint(fingerprint)))
 );
 
+
+COMMENT ON TABLE logintoken IS 'LoginToken stores one time tokens used by Launchpad for validating email addresses and other tasks that require verifying an email address is valid such as password recovery and account merging. This table will be cleaned occasionally to remove expired tokens. Expiry time is not yet defined.';
+
+
+COMMENT ON COLUMN logintoken.requester IS 'The Person that made this request. This will be null for password recovery requests.';
+
+
+COMMENT ON COLUMN logintoken.requesteremail IS 'The email address that was used to login when making this request. This provides an audit trail to help the end user confirm that this is a valid request. It is not a link to the EmailAddress table as this may be changed after the request is made. This field will be null for password recovery requests.';
+
+
+COMMENT ON COLUMN logintoken.email IS 'The email address that this request was sent to.';
+
+
+COMMENT ON COLUMN logintoken.created IS 'The timestamp that this request was made.';
+
+
+COMMENT ON COLUMN logintoken.tokentype IS 'The type of request, as per dbschema.TokenType.';
+
+
+COMMENT ON COLUMN logintoken.token IS 'The token (not the URL) emailed used to uniquely identify this request. This token will be used to generate a URL that when clicked on will continue a workflow.';
+
+
+COMMENT ON COLUMN logintoken.fingerprint IS 'The GPG key fingerprint to be validated on this transaction, it means that a new register will be created relating this given key with the requester in question. The requesteremail still passing for the same usual checks.';
+
+
+COMMENT ON COLUMN logintoken.date_consumed IS 'The date and time when this token was consumed. It''s NULL if it hasn''t been consumed yet.';
+
+
 CREATE SEQUENCE logintoken_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE logintoken_id_seq OWNED BY logintoken.id;
 
+
 CREATE TABLE lp_account (
     id integer NOT NULL,
     openid_identifier text NOT NULL
 );
 
+
+CREATE TABLE lp_openididentifier (
+    identifier text NOT NULL,
+    account integer NOT NULL,
+    date_created timestamp without time zone NOT NULL
+);
+
+
 CREATE TABLE lp_person (
     id integer NOT NULL,
     displayname text,
@@ -2765,6 +10365,7 @@
     account integer
 );
 
+
 CREATE TABLE lp_personlocation (
     id integer NOT NULL,
     date_created timestamp without time zone,
@@ -2778,12 +10379,14 @@
     locked boolean
 );
 
+
 CREATE TABLE lp_teamparticipation (
     id integer NOT NULL,
     team integer,
     person integer
 );
 
+
 CREATE TABLE mailinglist (
     id integer NOT NULL,
     team integer NOT NULL,
@@ -2796,29 +10399,44 @@
     welcome_message text
 );
 
+
+COMMENT ON TABLE mailinglist IS 'The mailing list for a team.  Teams may have zero or one mailing list, and a mailing list is associated with exactly one team.  This table manages the state changes that a team mailing list can go through, and it contains information that will be used to instruct Mailman how to create, delete, and modify mailing lists (via XMLRPC).';
+
+
+COMMENT ON COLUMN mailinglist.team IS 'The team this mailing list is associated with.';
+
+
+COMMENT ON COLUMN mailinglist.registrant IS 'The id of the Person who requested this list be created.';
+
+
+COMMENT ON COLUMN mailinglist.date_registered IS 'Date the list was requested to be created';
+
+
+COMMENT ON COLUMN mailinglist.reviewer IS 'The id of the Person who reviewed the creation request, or NULL if not yet reviewed.';
+
+
+COMMENT ON COLUMN mailinglist.date_reviewed IS 'The date the request was reviewed, or NULL if not yet reviewed.';
+
+
+COMMENT ON COLUMN mailinglist.date_activated IS 'The date the list was (last) activated.  If the list is not yet active, this field will be NULL.';
+
+
+COMMENT ON COLUMN mailinglist.status IS 'The current status of the mailing list, as a dbschema.MailingListStatus value.';
+
+
+COMMENT ON COLUMN mailinglist.welcome_message IS 'Text sent to new members when they are subscribed to the team list.  If NULL, no welcome message is sent.';
+
+
 CREATE SEQUENCE mailinglist_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mailinglist_id_seq OWNED BY mailinglist.id;
 
-CREATE TABLE mailinglistban (
-    id integer NOT NULL,
-    person integer NOT NULL,
-    banned_by integer NOT NULL,
-    date_banned timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    reason text NOT NULL
-);
-
-CREATE SEQUENCE mailinglistban_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE mailinglistban_id_seq OWNED BY mailinglistban.id;
 
 CREATE TABLE mailinglistsubscription (
     id integer NOT NULL,
@@ -2828,31 +10446,32 @@
     email_address integer
 );
 
+
+COMMENT ON TABLE mailinglistsubscription IS 'Track the subscriptions of a person to team mailing lists.';
+
+
+COMMENT ON COLUMN mailinglistsubscription.person IS 'The person who is subscribed to the mailing list.';
+
+
+COMMENT ON COLUMN mailinglistsubscription.mailing_list IS 'The mailing list this person is subscribed to.';
+
+
+COMMENT ON COLUMN mailinglistsubscription.date_joined IS 'The date this person subscribed to the mailing list.';
+
+
+COMMENT ON COLUMN mailinglistsubscription.email_address IS 'Which of the person''s email addresses are subscribed to the mailing list.  This may be NULL to indicate that it''s the person''s preferred address.';
+
+
 CREATE SEQUENCE mailinglistsubscription_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mailinglistsubscription_id_seq OWNED BY mailinglistsubscription.id;
 
-CREATE TABLE mentoringoffer (
-    id integer NOT NULL,
-    owner integer NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    team integer NOT NULL,
-    bug integer,
-    specification integer,
-    CONSTRAINT context_required CHECK (((bug IS NULL) <> (specification IS NULL)))
-);
-
-CREATE SEQUENCE mentoringoffer_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE mentoringoffer_id_seq OWNED BY mentoringoffer.id;
 
 CREATE TABLE mergedirectivejob (
     id integer NOT NULL,
@@ -2861,14 +10480,30 @@
     action integer NOT NULL
 );
 
+
+COMMENT ON TABLE mergedirectivejob IS 'A job to process a merge directive.';
+
+
+COMMENT ON COLUMN mergedirectivejob.job IS 'The job associated with this MergeDirectiveJob.';
+
+
+COMMENT ON COLUMN mergedirectivejob.merge_directive IS 'Full MIME content of the message containing the merge directive.';
+
+
+COMMENT ON COLUMN mergedirectivejob.action IS 'Enumeration of the action to perform with the merge directive; push or create merge proposal.';
+
+
 CREATE SEQUENCE mergedirectivejob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mergedirectivejob_id_seq OWNED BY mergedirectivejob.id;
 
+
 CREATE TABLE message (
     id integer NOT NULL,
     datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
@@ -2878,17 +10513,38 @@
     distribution integer,
     rfc822msgid text NOT NULL,
     fti ts2.tsvector,
-    raw integer
-);
+    raw integer,
+    visible boolean DEFAULT true NOT NULL
+)
+WITH (fillfactor=100);
+
+
+COMMENT ON TABLE message IS 'This table stores a single RFC822-style message. Messages can be threaded (using the parent field). These messages can then be referenced from elsewhere in the system, such as the BugMessage table, integrating messageboard facilities with the rest of The Launchpad.';
+
+
+COMMENT ON COLUMN message.subject IS 'The title text of the message, or the subject if it was an email.';
+
+
+COMMENT ON COLUMN message.parent IS 'A "parent message". This allows for some level of threading in Messages.';
+
+
+COMMENT ON COLUMN message.distribution IS 'The distribution in which this message originated, if we know it.';
+
+
+COMMENT ON COLUMN message.raw IS 'The original unadulterated message if it arrived via email. This is required to provide access to the original, undecoded message.';
+
 
 CREATE SEQUENCE message_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE message_id_seq OWNED BY message.id;
 
+
 CREATE TABLE messageapproval (
     id integer NOT NULL,
     posted_by integer NOT NULL,
@@ -2902,14 +10558,48 @@
     message integer NOT NULL
 );
 
+
+COMMENT ON TABLE messageapproval IS 'Track mailing list postings awaiting approval from the team owner.';
+
+
+COMMENT ON COLUMN messageapproval.posted_by IS 'The person who posted the message.';
+
+
+COMMENT ON COLUMN messageapproval.mailing_list IS 'The mailing list to which the message was posted.';
+
+
+COMMENT ON COLUMN messageapproval.posted_message IS 'Foreign key to libraryfilealias table pointing to where the posted message''s text lives.';
+
+
+COMMENT ON COLUMN messageapproval.posted_date IS 'The date the message was posted.';
+
+
+COMMENT ON COLUMN messageapproval.status IS 'The status of the posted message.  Values are described in dbschema.PostedMessageStatus.';
+
+
+COMMENT ON COLUMN messageapproval.disposed_by IS 'The person who disposed of (i.e. approved or rejected) the message, or NULL if no disposition has yet been made.';
+
+
+COMMENT ON COLUMN messageapproval.disposal_date IS 'The date on which this message was disposed, or NULL if no disposition has yet been made.';
+
+
+COMMENT ON COLUMN messageapproval.reason IS 'The reason for the current status if any. This information will be displayed to the end user and mailing list moderators need to be aware of this - not a private whiteboard.';
+
+
+COMMENT ON COLUMN messageapproval.message IS 'Foreign key to message table pointing to the posted message.';
+
+
 CREATE SEQUENCE messageapproval_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE messageapproval_id_seq OWNED BY messageapproval.id;
 
+
 CREATE TABLE messagechunk (
     id integer NOT NULL,
     message integer NOT NULL,
@@ -2918,16 +10608,33 @@
     blob integer,
     fti ts2.tsvector,
     CONSTRAINT text_or_content CHECK ((((blob IS NULL) AND (content IS NULL)) OR ((blob IS NULL) <> (content IS NULL))))
-);
+)
+WITH (fillfactor=100);
+
+
+COMMENT ON TABLE messagechunk IS 'This table stores a single chunk of a possibly multipart message. There will be at least one row in this table for each message. text/* parts are stored in the content column. All other parts are stored in the Librarian and referenced via the blob column. If both content and blob are NULL, then this chunk has been removed (eg. offensive, legal reasons, virus etc.)';
+
+
+COMMENT ON COLUMN messagechunk.sequence IS 'Order of a particular chunk. Chunks are orders in ascending order starting from 1.';
+
+
+COMMENT ON COLUMN messagechunk.content IS 'Text content for this chunk of the message. This content is full text searchable.';
+
+
+COMMENT ON COLUMN messagechunk.blob IS 'Binary content for this chunk of the message.';
+
 
 CREATE SEQUENCE messagechunk_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE messagechunk_id_seq OWNED BY messagechunk.id;
 
+
 CREATE TABLE milestone (
     id integer NOT NULL,
     product integer,
@@ -2944,14 +10651,48 @@
     CONSTRAINT valid_target CHECK ((NOT ((product IS NULL) AND (distribution IS NULL))))
 );
 
+
+COMMENT ON TABLE milestone IS 'An identifier that helps a maintainer group together things in some way, e.g. "1.2" could be a Milestone that bazaar developers could use to mark a task as needing fixing in bazaar 1.2.';
+
+
+COMMENT ON COLUMN milestone.product IS 'The product for which this is a milestone.';
+
+
+COMMENT ON COLUMN milestone.name IS 'The identifier text, e.g. "1.2."';
+
+
+COMMENT ON COLUMN milestone.distribution IS 'The distribution to which this milestone belongs, if it is a distro milestone.';
+
+
+COMMENT ON COLUMN milestone.dateexpected IS 'If set, the date on which we expect this milestone to be delivered. This allows for optional sorting by date.';
+
+
+COMMENT ON COLUMN milestone.active IS 'Whether or not this milestone should be displayed in general listings. All milestones will be visible on the "page of milestones for product foo", but we want to be able to screen out obviously old milestones over time, for the general listings and vocabularies.';
+
+
+COMMENT ON COLUMN milestone.productseries IS 'The productseries for which this is a milestone. A milestone on a productseries is ALWAYS also a milestone for the same product. This is because milestones started out on products/distributions but are moving to being on series/distroseries.';
+
+
+COMMENT ON COLUMN milestone.distroseries IS 'The distroseries for which this is a milestone. A milestone on a distroseries is ALWAYS also a milestone for the same distribution. This is because milestones started out on products/distributions but are moving to being on series/distroseries.';
+
+
+COMMENT ON COLUMN milestone.summary IS 'This can be used to summarize the changes included in past milestones and to document the status of current milestones.';
+
+
+COMMENT ON COLUMN milestone.codename IS 'A fun or easier to remember name for the milestone/release.';
+
+
 CREATE SEQUENCE milestone_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE milestone_id_seq OWNED BY milestone.id;
 
+
 CREATE TABLE mirror (
     id integer NOT NULL,
     owner integer NOT NULL,
@@ -2965,14 +10706,42 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE mirror IS 'Stores general information about mirror sites. Both regular pull mirrors and top tier mirrors are included.';
+
+
+COMMENT ON COLUMN mirror.baseurl IS 'The base URL to the mirror, including protocol and optional trailing slash.';
+
+
+COMMENT ON COLUMN mirror.country IS 'The country where the mirror is located.';
+
+
+COMMENT ON COLUMN mirror.name IS 'Unique name for the mirror, suitable for use in URLs.';
+
+
+COMMENT ON COLUMN mirror.description IS 'Description of the mirror.';
+
+
+COMMENT ON COLUMN mirror.freshness IS 'dbschema.MirrorFreshness enumeration indicating freshness.';
+
+
+COMMENT ON COLUMN mirror.lastcheckeddate IS 'UTC timestamp of when the last check for freshness and consistency was made. NULL indicates no check has ever been made.';
+
+
+COMMENT ON COLUMN mirror.approved IS 'True if this mirror has been approved by the Ubuntu/Canonical mirror manager, otherwise False.';
+
+
 CREATE SEQUENCE mirror_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mirror_id_seq OWNED BY mirror.id;
 
+
 CREATE TABLE mirrorcdimagedistroseries (
     id integer NOT NULL,
     distribution_mirror integer NOT NULL,
@@ -2981,14 +10750,30 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE mirrorcdimagedistroseries IS 'The mirror of a given CD/DVD image.';
+
+
+COMMENT ON COLUMN mirrorcdimagedistroseries.distribution_mirror IS 'The distribution mirror.';
+
+
+COMMENT ON COLUMN mirrorcdimagedistroseries.distroseries IS 'The Distribution Release.';
+
+
+COMMENT ON COLUMN mirrorcdimagedistroseries.flavour IS 'The Distribution Release Flavour.';
+
+
 CREATE SEQUENCE mirrorcdimagedistroseries_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mirrorcdimagedistroseries_id_seq OWNED BY mirrorcdimagedistroseries.id;
 
+
 CREATE TABLE mirrorcontent (
     id integer NOT NULL,
     mirror integer NOT NULL,
@@ -2997,14 +10782,27 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE mirrorcontent IS 'Stores which distroarchseries and compoenents a given mirror has.';
+
+
+COMMENT ON COLUMN mirrorcontent.distroarchseries IS 'A distroarchseries that this mirror contains.';
+
+
+COMMENT ON COLUMN mirrorcontent.component IS 'What component of the distroarchseries that this mirror contains.';
+
+
 CREATE SEQUENCE mirrorcontent_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mirrorcontent_id_seq OWNED BY mirrorcontent.id;
 
+
 CREATE TABLE mirrordistroarchseries (
     id integer NOT NULL,
     distribution_mirror integer NOT NULL,
@@ -3015,14 +10813,33 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE mirrordistroarchseries IS 'The mirror of the packages of a given Distro Arch Release.';
+
+
+COMMENT ON COLUMN mirrordistroarchseries.distribution_mirror IS 'The distribution mirror.';
+
+
+COMMENT ON COLUMN mirrordistroarchseries.distroarchseries IS 'The distro arch series.';
+
+
+COMMENT ON COLUMN mirrordistroarchseries.freshness IS 'The freshness of the mirror, that is, how up-to-date it is.';
+
+
+COMMENT ON COLUMN mirrordistroarchseries.pocket IS 'The PackagePublishingPocket.';
+
+
 CREATE SEQUENCE mirrordistroarchseries_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mirrordistroarchseries_id_seq OWNED BY mirrordistroarchseries.id;
 
+
 CREATE TABLE mirrordistroseriessource (
     id integer NOT NULL,
     distribution_mirror integer NOT NULL,
@@ -3033,14 +10850,30 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE mirrordistroseriessource IS 'The mirror of a given Distro Release';
+
+
+COMMENT ON COLUMN mirrordistroseriessource.distribution_mirror IS 'The distribution mirror.';
+
+
+COMMENT ON COLUMN mirrordistroseriessource.distroseries IS 'The Distribution Release.';
+
+
+COMMENT ON COLUMN mirrordistroseriessource.freshness IS 'The freshness of the mirror, that is, how up-to-date it is.';
+
+
 CREATE SEQUENCE mirrordistroseriessource_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mirrordistroseriessource_id_seq OWNED BY mirrordistroseriessource.id;
 
+
 CREATE TABLE mirrorproberecord (
     id integer NOT NULL,
     distribution_mirror integer NOT NULL,
@@ -3048,14 +10881,30 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL
 );
 
+
+COMMENT ON TABLE mirrorproberecord IS 'Records stored when a mirror is probed.';
+
+
+COMMENT ON COLUMN mirrorproberecord.distribution_mirror IS 'The DistributionMirror.';
+
+
+COMMENT ON COLUMN mirrorproberecord.log_file IS 'The log file of the probe.';
+
+
+COMMENT ON COLUMN mirrorproberecord.date_created IS 'The date and time the probe was performed.';
+
+
 CREATE SEQUENCE mirrorproberecord_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mirrorproberecord_id_seq OWNED BY mirrorproberecord.id;
 
+
 CREATE TABLE mirrorsourcecontent (
     id integer NOT NULL,
     mirror integer NOT NULL,
@@ -3064,29 +10913,56 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE mirrorsourcecontent IS 'Stores which distroseries and components a given mirror that includes source packages has.';
+
+
+COMMENT ON COLUMN mirrorsourcecontent.distroseries IS 'A distroseries that this mirror contains.';
+
+
+COMMENT ON COLUMN mirrorsourcecontent.component IS 'What component of the distroseries that this sourcepackage mirror contains.';
+
+
 CREATE SEQUENCE mirrorsourcecontent_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE mirrorsourcecontent_id_seq OWNED BY mirrorsourcecontent.id;
 
+
 CREATE TABLE nameblacklist (
     id integer NOT NULL,
     regexp text NOT NULL,
     comment text,
+    admin integer,
     CONSTRAINT valid_regexp CHECK (valid_regexp(regexp))
 );
 
+
+COMMENT ON TABLE nameblacklist IS 'A list of regular expressions used to blacklist names.';
+
+
+COMMENT ON COLUMN nameblacklist.regexp IS 'A Python regular expression. It will be compiled with the IGNORECASE, UNICODE and VERBOSE flags. The Python search method will be used rather than match, so ^ markers should be used to indicate the start of a string.';
+
+
+COMMENT ON COLUMN nameblacklist.comment IS 'An optional comment on why this regexp was entered. It should not be displayed to non-admins and its only purpose is documentation.';
+
+
 CREATE SEQUENCE nameblacklist_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE nameblacklist_id_seq OWNED BY nameblacklist.id;
 
+
 CREATE TABLE oauthaccesstoken (
     id integer NOT NULL,
     consumer integer NOT NULL,
@@ -3104,14 +10980,55 @@
     CONSTRAINT sourcepackagename_needs_distro CHECK (((sourcepackagename IS NULL) OR (distribution IS NOT NULL)))
 );
 
+
+COMMENT ON TABLE oauthaccesstoken IS 'An access token used by the consumer to act on behalf of one of our users.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.consumer IS 'The consumer which is going to access the protected resources.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.person IS 'The person on whose behalf the
+consumer will access Launchpad.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.permission IS 'The permission given by that person to the consumer.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.date_created IS 'The date/time in which the token was created.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.date_expires IS 'The date/time in which this token will stop being accepted by Launchpad.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.key IS 'This token''s unique key.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.secret IS 'The secret used by the consumer (together with the token''s key) to access Launchpad on behalf of the person.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.product IS 'The product associated with this token.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.project IS 'The project associated with this token.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.distribution IS 'The distribution associated with this token.';
+
+
+COMMENT ON COLUMN oauthaccesstoken.sourcepackagename IS 'The sourcepackagename associated with this token.';
+
+
 CREATE SEQUENCE oauthaccesstoken_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE oauthaccesstoken_id_seq OWNED BY oauthaccesstoken.id;
 
+
 CREATE TABLE oauthconsumer (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -3120,28 +11037,51 @@
     secret text
 );
 
+
+COMMENT ON TABLE oauthconsumer IS 'A third part application that will access Launchpad on behalf of one of our users.';
+
+
+COMMENT ON COLUMN oauthconsumer.date_created IS 'The creation date.';
+
+
+COMMENT ON COLUMN oauthconsumer.disabled IS 'Is this consumer disabled?';
+
+
+COMMENT ON COLUMN oauthconsumer.key IS 'The unique key for this consumer.';
+
+
+COMMENT ON COLUMN oauthconsumer.secret IS 'The secret used by this consumer (together with its key) to identify itself with Launchpad.';
+
+
 CREATE SEQUENCE oauthconsumer_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE oauthconsumer_id_seq OWNED BY oauthconsumer.id;
 
+
 CREATE TABLE oauthnonce (
-    id integer NOT NULL,
     request_timestamp timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     nonce text NOT NULL,
     access_token integer NOT NULL
 );
 
-CREATE SEQUENCE oauthnonce_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE oauthnonce_id_seq OWNED BY oauthnonce.id;
+
+COMMENT ON TABLE oauthnonce IS 'The unique nonce for any request with a given timestamp and access token. This is generated by the consumer.';
+
+
+COMMENT ON COLUMN oauthnonce.request_timestamp IS 'The date and time (as a timestamp) in which the request was made.';
+
+
+COMMENT ON COLUMN oauthnonce.nonce IS 'The nonce itself.';
+
+
+COMMENT ON COLUMN oauthnonce.access_token IS 'The access token.';
+
 
 CREATE TABLE oauthrequesttoken (
     id integer NOT NULL,
@@ -3162,14 +11102,58 @@
     CONSTRAINT sourcepackagename_needs_distro CHECK (((sourcepackagename IS NULL) OR (distribution IS NOT NULL)))
 );
 
+
+COMMENT ON TABLE oauthrequesttoken IS 'A request token which, once authorized by the user, is exchanged for an access token.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.consumer IS 'The consumer which is going to access the protected resources.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.person IS 'The person who authorized this token.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.permission IS 'The permission given by the
+person to the consumer.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.date_expires IS 'When the authorization is to expire.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.date_created IS 'The date/time in which the token was created.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.date_reviewed IS 'When the authorization request was authorized or rejected by the person.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.key IS 'This token''s unique key.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.secret IS 'The secret used by the consumer (together with the token''s key) to get an access token once the user has authorized its use.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.product IS 'The product associated with this token.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.project IS 'The project associated with this token.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.distribution IS 'The distribution associated with this token.';
+
+
+COMMENT ON COLUMN oauthrequesttoken.sourcepackagename IS 'The sourcepackagename associated with this token.';
+
+
 CREATE SEQUENCE oauthrequesttoken_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE oauthrequesttoken_id_seq OWNED BY oauthrequesttoken.id;
 
+
 CREATE TABLE officialbugtag (
     id integer NOT NULL,
     tag text NOT NULL,
@@ -3179,23 +11163,20 @@
     CONSTRAINT context_required CHECK (((product IS NOT NULL) OR (distribution IS NOT NULL)))
 );
 
+
+COMMENT ON TABLE officialbugtag IS 'Bug tags that have been officially endorced by this product''s or distribution''s lead';
+
+
 CREATE SEQUENCE officialbugtag_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE officialbugtag_id_seq OWNED BY officialbugtag.id;
 
-CREATE TABLE openidassociation (
-    server_url character varying(2047) NOT NULL,
-    handle character varying(255) NOT NULL,
-    secret bytea,
-    issued integer,
-    lifetime integer,
-    assoc_type character varying(64),
-    CONSTRAINT secret_length_constraint CHECK ((length(secret) <= 128))
-);
 
 CREATE TABLE openidconsumerassociation (
     server_url character varying(2047) NOT NULL,
@@ -3207,65 +11188,26 @@
     CONSTRAINT secret_length_constraint CHECK ((length(secret) <= 128))
 );
 
+
 CREATE TABLE openidconsumernonce (
     server_url character varying(2047) NOT NULL,
     "timestamp" integer NOT NULL,
     salt character(40) NOT NULL
 );
 
-CREATE TABLE openidrpconfig (
-    id integer NOT NULL,
-    trust_root text NOT NULL,
-    displayname text NOT NULL,
-    description text NOT NULL,
-    logo integer,
-    allowed_sreg text,
-    creation_rationale integer DEFAULT 13 NOT NULL,
-    can_query_any_team boolean DEFAULT false NOT NULL,
-    auto_authorize boolean DEFAULT false NOT NULL
-);
-
-CREATE SEQUENCE openidrpconfig_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE openidrpconfig_id_seq OWNED BY openidrpconfig.id;
-
-CREATE TABLE openidrpsummary (
-    id integer NOT NULL,
+
+CREATE TABLE openididentifier (
+    identifier text NOT NULL,
     account integer NOT NULL,
-    openid_identifier text NOT NULL,
-    trust_root text NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    date_last_used timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    total_logins integer DEFAULT 1 NOT NULL
-);
-
-CREATE SEQUENCE openidrpsummary_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE openidrpsummary_id_seq OWNED BY openidrpsummary.id;
-
-CREATE TABLE packagebugsupervisor (
-    id integer NOT NULL,
-    distribution integer NOT NULL,
-    sourcepackagename integer NOT NULL,
-    bug_supervisor integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
-CREATE SEQUENCE packagebugsupervisor_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE packagebugsupervisor_id_seq OWNED BY packagebugsupervisor.id;
+
+COMMENT ON TABLE openididentifier IS 'OpenId Identifiers that can be used to log into an Account.';
+
+
+COMMENT ON COLUMN openididentifier.identifier IS 'OpenId Identifier. This should be a URL, but is currently just a token that can be used to generate the Identity URL for the Canonical SSO OpenId Provider.';
+
 
 CREATE TABLE packagebuild (
     id integer NOT NULL,
@@ -3276,14 +11218,60 @@
     dependencies text
 );
 
+
+COMMENT ON TABLE packagebuild IS 'PackageBuild: This table stores the information common to build farm jobs that build source or binary packages.';
+
+
+COMMENT ON COLUMN packagebuild.build_farm_job IS 'Points to the build farm job with the base information.';
+
+
+COMMENT ON COLUMN packagebuild.archive IS 'Targeted archive for this package build.';
+
+
+COMMENT ON COLUMN packagebuild.pocket IS 'Stores the target pocket identifier for this package build.';
+
+
+COMMENT ON COLUMN packagebuild.upload_log IS 'Reference to a LibraryFileAlias containing the upload log messages generated while processing the packages resulting from this package build.';
+
+
+COMMENT ON COLUMN packagebuild.dependencies IS 'Contains a debian-like dependency line specifying the current missing-dependencies for this package.';
+
+
 CREATE SEQUENCE packagebuild_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packagebuild_id_seq OWNED BY packagebuild.id;
 
+
+CREATE TABLE packagecopyjob (
+    id integer NOT NULL,
+    job integer NOT NULL,
+    source_archive integer NOT NULL,
+    target_archive integer NOT NULL,
+    target_distroseries integer,
+    job_type integer NOT NULL,
+    json_data text,
+    package_name text NOT NULL,
+    copy_policy integer
+);
+
+
+CREATE SEQUENCE packagecopyjob_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE packagecopyjob_id_seq OWNED BY packagecopyjob.id;
+
+
 CREATE TABLE packagecopyrequest (
     id integer NOT NULL,
     target_archive integer NOT NULL,
@@ -3303,14 +11291,63 @@
     date_completed timestamp without time zone
 );
 
+
+COMMENT ON TABLE packagecopyrequest IS 'PackageCopyRequest: A table that captures the status and the details of an inter-archive package copy operation.';
+
+
+COMMENT ON COLUMN packagecopyrequest.target_archive IS 'The archive to which packages will be copied.';
+
+
+COMMENT ON COLUMN packagecopyrequest.target_distroseries IS 'The target distroseries.';
+
+
+COMMENT ON COLUMN packagecopyrequest.target_component IS 'The target component.';
+
+
+COMMENT ON COLUMN packagecopyrequest.target_pocket IS 'The target pocket.';
+
+
+COMMENT ON COLUMN packagecopyrequest.source_archive IS 'The archive from which packages are to be copied.';
+
+
+COMMENT ON COLUMN packagecopyrequest.source_distroseries IS 'The distroseries to which the packages to be copied belong in the source archive.';
+
+
+COMMENT ON COLUMN packagecopyrequest.source_component IS 'The component to which the packages to be copied belong in the source archive.';
+
+
+COMMENT ON COLUMN packagecopyrequest.source_pocket IS 'The pocket for the packages to be copied.';
+
+
+COMMENT ON COLUMN packagecopyrequest.requester IS 'The person who requested the archive operation.';
+
+
+COMMENT ON COLUMN packagecopyrequest.status IS 'Archive operation status, may be one of: new, in-progress, complete, failed, cancelling, cancelled.';
+
+
+COMMENT ON COLUMN packagecopyrequest.reason IS 'The reason why this copy operation was requested.';
+
+
+COMMENT ON COLUMN packagecopyrequest.date_created IS 'Date of creation for this archive operation.';
+
+
+COMMENT ON COLUMN packagecopyrequest.date_started IS 'Start date/time of this archive operation.';
+
+
+COMMENT ON COLUMN packagecopyrequest.date_completed IS 'When did this archive operation conclude?';
+
+
 CREATE SEQUENCE packagecopyrequest_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packagecopyrequest_id_seq OWNED BY packagecopyrequest.id;
 
+
 CREATE TABLE packagediff (
     id integer NOT NULL,
     date_requested timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -3323,33 +11360,41 @@
     CONSTRAINT distinct_sources CHECK ((from_source <> to_source))
 );
 
+
+COMMENT ON TABLE packagediff IS 'This table stores diffs bettwen two scpecific SourcePackageRelease versions.';
+
+
+COMMENT ON COLUMN packagediff.date_requested IS 'Instant when the diff was requested.';
+
+
+COMMENT ON COLUMN packagediff.requester IS 'The Person responsible for the request.';
+
+
+COMMENT ON COLUMN packagediff.from_source IS 'The SourcePackageRelease to diff from.';
+
+
+COMMENT ON COLUMN packagediff.to_source IS 'The SourcePackageRelease to diff to.';
+
+
+COMMENT ON COLUMN packagediff.date_fulfilled IS 'Instant when the diff was completed.';
+
+
+COMMENT ON COLUMN packagediff.diff_content IS 'LibraryFileAlias containing the th diff results.';
+
+
+COMMENT ON COLUMN packagediff.status IS 'Request status, PENDING(0) when created then goes to COMPLETED(1) or FAILED(2), both terminal status where diff_content and date_fulfilled will contain the results of the request.';
+
+
 CREATE SEQUENCE packagediff_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packagediff_id_seq OWNED BY packagediff.id;
 
-CREATE TABLE packageselection (
-    id integer NOT NULL,
-    distroseries integer NOT NULL,
-    sourcepackagename integer,
-    binarypackagename integer,
-    action integer NOT NULL,
-    component integer,
-    section integer,
-    priority integer,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
-);
-
-CREATE SEQUENCE packageselection_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE packageselection_id_seq OWNED BY packageselection.id;
 
 CREATE TABLE packageset (
     id integer NOT NULL,
@@ -3362,34 +11407,77 @@
     CONSTRAINT packageset_name_check CHECK (valid_name(name))
 );
 
+
+COMMENT ON TABLE packageset IS 'Package sets facilitate the grouping of packages (in a given distro series) for purposes like the control of upload permissions, etc.';
+
+
+COMMENT ON COLUMN packageset.date_created IS 'Date and time of creation.';
+
+
+COMMENT ON COLUMN packageset.owner IS 'The Person or team who owns the package
+set group.';
+
+
+COMMENT ON COLUMN packageset.name IS 'The name for the package set on hand.';
+
+
+COMMENT ON COLUMN packageset.description IS 'The description for the package set on hand.';
+
+
+COMMENT ON COLUMN packageset.packagesetgroup IS 'The group this package set is affiliated with.';
+
+
+COMMENT ON COLUMN packageset.distroseries IS 'The distro series this package set belongs to.';
+
+
 CREATE SEQUENCE packageset_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packageset_id_seq OWNED BY packageset.id;
 
+
 CREATE TABLE packagesetgroup (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     owner integer NOT NULL
 );
 
+
+COMMENT ON TABLE packagesetgroup IS 'Package set groups keep track of equivalent package sets across distro series boundaries.';
+
+
 CREATE SEQUENCE packagesetgroup_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packagesetgroup_id_seq OWNED BY packagesetgroup.id;
 
+
 CREATE TABLE packagesetinclusion (
     id integer NOT NULL,
     parent integer NOT NULL,
     child integer NOT NULL
 );
 
+
+COMMENT ON TABLE packagesetinclusion IS 'sets may form a set-subset hierarchy; this table facilitates the definition of these set-subset relationships.';
+
+
+COMMENT ON COLUMN packagesetinclusion.parent IS 'The package set that is including a subset.';
+
+
+COMMENT ON COLUMN packagesetinclusion.child IS 'The package set that is being included as a subset.';
+
+
 CREATE SEQUENCE packagesetinclusion_id_seq
     START WITH 1
     INCREMENT BY 1
@@ -3397,22 +11485,37 @@
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packagesetinclusion_id_seq OWNED BY packagesetinclusion.id;
 
+
 CREATE TABLE packagesetsources (
     id integer NOT NULL,
     packageset integer NOT NULL,
     sourcepackagename integer NOT NULL
 );
 
+
+COMMENT ON TABLE packagesetsources IS 'This table associates package sets and source package names.';
+
+
+COMMENT ON COLUMN packagesetsources.packageset IS 'The associated package set.';
+
+
+COMMENT ON COLUMN packagesetsources.sourcepackagename IS 'The associated source package name.';
+
+
 CREATE SEQUENCE packagesetsources_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packagesetsources_id_seq OWNED BY packagesetsources.id;
 
+
 CREATE TABLE packageupload (
     id integer NOT NULL,
     status integer DEFAULT 0 NOT NULL,
@@ -3421,17 +11524,40 @@
     changesfile integer,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     signing_key integer,
-    archive integer NOT NULL
+    archive integer NOT NULL,
+    package_copy_job integer
 );
 
+
+COMMENT ON TABLE packageupload IS 'An upload. This table stores information pertaining to uploads to a given DistroSeries/Archive.';
+
+
+COMMENT ON COLUMN packageupload.status IS 'This is an integer field containing the current status of the upload. Possible values are given by the UploadStatus class in dbschema.py';
+
+
+COMMENT ON COLUMN packageupload.distroseries IS 'This integer field refers to the DistroSeries to which this upload is targeted';
+
+
+COMMENT ON COLUMN packageupload.pocket IS 'This is the pocket the upload is targeted at.';
+
+
+COMMENT ON COLUMN packageupload.changesfile IS 'The changes file associated with this upload. It is null for records refering to a delayed-copy.';
+
+
+COMMENT ON COLUMN packageupload.archive IS 'The archive to which this upload is targetted.';
+
+
 CREATE SEQUENCE packageupload_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packageupload_id_seq OWNED BY packageupload.id;
 
+
 CREATE TABLE packageuploadbuild (
     id integer NOT NULL,
     packageupload integer NOT NULL,
@@ -3439,14 +11565,27 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE packageuploadbuild IS 'An upload binary build. This table stores information pertaining to the builds in a package upload.';
+
+
+COMMENT ON COLUMN packageuploadbuild.packageupload IS 'This integer field refers to the PackageUpload row that this source belongs to.';
+
+
+COMMENT ON COLUMN packageuploadbuild.build IS 'This integer field refers to the Build record related to this upload.';
+
+
 CREATE SEQUENCE packageuploadbuild_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packageuploadbuild_id_seq OWNED BY packageuploadbuild.id;
 
+
 CREATE TABLE packageuploadcustom (
     id integer NOT NULL,
     packageupload integer NOT NULL,
@@ -3455,14 +11594,30 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE packageuploadcustom IS 'An uploaded custom format file. This table stores information pertaining to the custom upload formats in a package upload.';
+
+
+COMMENT ON COLUMN packageuploadcustom.packageupload IS 'The PackageUpload row this refers to.';
+
+
+COMMENT ON COLUMN packageuploadcustom.customformat IS 'The format of this particular custom uploaded file.';
+
+
+COMMENT ON COLUMN packageuploadcustom.libraryfilealias IS 'The actual file as a librarian alias.';
+
+
 CREATE SEQUENCE packageuploadcustom_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packageuploadcustom_id_seq OWNED BY packageuploadcustom.id;
 
+
 CREATE TABLE packageuploadsource (
     id integer NOT NULL,
     packageupload integer NOT NULL,
@@ -3470,56 +11625,268 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE packageuploadsource IS 'Link between an upload and a source package. This table stores information pertaining to the source files in a package upload.';
+
+
+COMMENT ON COLUMN packageuploadsource.packageupload IS 'This integer field refers to the PackageUpload row that this source belongs to.';
+
+
+COMMENT ON COLUMN packageuploadsource.sourcepackagerelease IS 'This integer field refers to the SourcePackageRelease record related to this upload.';
+
+
 CREATE SEQUENCE packageuploadsource_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packageuploadsource_id_seq OWNED BY packageuploadsource.id;
 
+
 CREATE TABLE packaging (
     packaging integer NOT NULL,
     id integer DEFAULT nextval(('packaging_id_seq'::text)::regclass) NOT NULL,
     sourcepackagename integer,
-    distroseries integer,
+    distroseries integer NOT NULL,
     productseries integer NOT NULL,
     datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
     owner integer,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE packaging IS 'DO NOT JOIN THROUGH THIS TABLE. This is a set
+of information linking upstream product series (branches) to distro
+packages, but it''s not planned or likely to be complete, in the sense that
+we do not attempt to have information for every branch in every derivative
+distro managed in Launchpad. So don''t join through this table to get from
+product to source package, or vice versa. Rather, use the
+ProductSeries.sourcepackages attribute, or the
+SourcePackage.productseries attribute. You may need to create a
+SourcePackage with a given sourcepackagename and distroseries, then use its
+.productrelease attribute. The code behind those methods does more than just
+join through the tables, it is also smart enough to look at related
+distro''s and parent distroseries, and at Ubuntu in particular.';
+
+
+COMMENT ON COLUMN packaging.packaging IS 'A dbschema Enum (PackagingType)
+describing the way the upstream productseries has been packaged. Generally
+it will be of type PRIME, meaning that the upstream productseries is the
+primary substance of the package, but it might also be INCLUDES, if the
+productseries has been included as a statically linked library, for example.
+This allows us to say that a given Source Package INCLUDES libneon but is a
+PRIME package of tla, for example. By INCLUDES we mean that the code is
+actually lumped into the package as ancilliary support material, rather
+than simply depending on a separate packaging of that code.';
+
+
+COMMENT ON COLUMN packaging.sourcepackagename IS 'The source package name for
+the source package that includes the upstream productseries described in
+this Packaging record. There is no requirement that such a sourcepackage
+actually be published in the distro.';
+
+
+COMMENT ON COLUMN packaging.distroseries IS 'The distroseries in which the
+productseries has been packaged.';
+
+
+COMMENT ON COLUMN packaging.productseries IS 'The upstream product series
+that has been packaged in this distroseries sourcepackage.';
+
+
+COMMENT ON COLUMN packaging.owner IS 'This is not the "owner" in the sense
+of giving the person any special privileges to edit the Packaging record,
+it is simply a record of who told us about this packaging relationship. Note
+that we do not keep a history of these, so if someone sets it correctly,
+then someone else sets it incorrectly, we lose the first setting.';
+
+
 CREATE SEQUENCE packaging_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE packaging_id_seq OWNED BY packaging.id;
 
+
+CREATE TABLE packagingjob (
+    id integer NOT NULL,
+    job integer NOT NULL,
+    job_type integer NOT NULL,
+    productseries integer,
+    sourcepackagename integer,
+    distroseries integer,
+    potemplate integer,
+    CONSTRAINT translationtemplatejob_valid_link CHECK ((((((potemplate IS NOT NULL) AND (productseries IS NULL)) AND (distroseries IS NULL)) AND (sourcepackagename IS NULL)) OR ((((potemplate IS NULL) AND (productseries IS NOT NULL)) AND (distroseries IS NOT NULL)) AND (sourcepackagename IS NOT NULL))))
+);
+
+
+CREATE SEQUENCE packagingjob_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE packagingjob_id_seq OWNED BY packagingjob.id;
+
+
 CREATE TABLE parsedapachelog (
     id integer NOT NULL,
     first_line text NOT NULL,
-    bytes_read integer NOT NULL,
+    bytes_read bigint NOT NULL,
     date_last_parsed timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE parsedapachelog IS 'A parsed apache log file for librarian.';
+
+
+COMMENT ON COLUMN parsedapachelog.first_line IS 'The first line of this log file, smashed to ASCII. This uniquely identifies the log file, even if its filename is changed by log rotation or archival.';
+
+
+COMMENT ON COLUMN parsedapachelog.bytes_read IS 'The number of bytes from this log file that have been parsed.';
+
+
 CREATE SEQUENCE parsedapachelog_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE parsedapachelog_id_seq OWNED BY parsedapachelog.id;
 
+
+CREATE TABLE person (
+    id integer NOT NULL,
+    displayname text NOT NULL,
+    teamowner integer,
+    teamdescription text,
+    name text NOT NULL,
+    language integer,
+    fti ts2.tsvector,
+    defaultmembershipperiod integer,
+    defaultrenewalperiod integer,
+    subscriptionpolicy integer DEFAULT 1 NOT NULL,
+    merged integer,
+    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
+    homepage_content text,
+    icon integer,
+    mugshot integer,
+    hide_email_addresses boolean DEFAULT false NOT NULL,
+    creation_rationale integer,
+    creation_comment text,
+    registrant integer,
+    logo integer,
+    renewal_policy integer DEFAULT 10 NOT NULL,
+    personal_standing integer DEFAULT 0 NOT NULL,
+    personal_standing_reason text,
+    mail_resumption_date date,
+    mailing_list_auto_subscribe_policy integer DEFAULT 1 NOT NULL,
+    mailing_list_receive_duplicates boolean DEFAULT true NOT NULL,
+    visibility integer DEFAULT 1 NOT NULL,
+    verbose_bugnotifications boolean DEFAULT false NOT NULL,
+    account integer,
+    description text,
+    CONSTRAINT creation_rationale_not_null_for_people CHECK (((creation_rationale IS NULL) = (teamowner IS NOT NULL))),
+    CONSTRAINT no_loops CHECK ((id <> teamowner)),
+    CONSTRAINT non_empty_displayname CHECK ((btrim(displayname) <> ''::text)),
+    CONSTRAINT people_have_no_emblems CHECK (((icon IS NULL) OR (teamowner IS NOT NULL))),
+    CONSTRAINT sane_defaultrenewalperiod CHECK (CASE WHEN (teamowner IS NULL) THEN (defaultrenewalperiod IS NULL) WHEN (renewal_policy = ANY (ARRAY[20, 30])) THEN ((defaultrenewalperiod IS NOT NULL) AND (defaultrenewalperiod > 0)) ELSE ((defaultrenewalperiod IS NULL) OR (defaultrenewalperiod > 0)) END),
+    CONSTRAINT teams_have_no_account CHECK (((account IS NULL) OR (teamowner IS NULL))),
+    CONSTRAINT valid_name CHECK (valid_name(name))
+);
+
+
+COMMENT ON TABLE person IS 'A row represents a person if teamowner is NULL, and represents a team if teamowner is set.';
+
+
+COMMENT ON COLUMN person.displayname IS 'Person or group''s name as it should be rendered to screen';
+
+
+COMMENT ON COLUMN person.teamowner IS 'id of the team owner. Team owners will have authority to add or remove people from the team.';
+
+
+COMMENT ON COLUMN person.teamdescription IS 'Informative description of the team. Format and restrictions are as yet undefined.';
+
+
+COMMENT ON COLUMN person.name IS 'Short mneumonic name uniquely identifying this person or team. Useful for url traversal or in places where we need to unambiguously refer to a person or team (as displayname is not unique).';
+
+
+COMMENT ON COLUMN person.language IS 'Preferred language for this person (unset for teams). UI should be displayed in this language wherever possible.';
+
+
+COMMENT ON COLUMN person.subscriptionpolicy IS 'The policy for new members to join this team.';
+
+
+COMMENT ON COLUMN person.homepage_content IS 'A home page for this person in the Launchpad. In short, this is like a personal wiki page. The person will get to edit their own page, and it will be published on /people/foo/. Note that this is in text format, and will migrate to being in Moin format as a sort of mini-wiki-homepage.';
+
+
+COMMENT ON COLUMN person.icon IS 'The library file alias to a small image to be used as an icon whenever we are referring to that person.';
+
+
+COMMENT ON COLUMN person.mugshot IS 'The library file alias of a hackermugshot image to display as the "face" of a person, on their home page.';
+
+
+COMMENT ON COLUMN person.creation_rationale IS 'The rationale for the creation of this person -- a dbschema value.';
+
+
+COMMENT ON COLUMN person.creation_comment IS 'A text comment for the creation of this person.';
+
+
+COMMENT ON COLUMN person.registrant IS 'The user who created this profile.';
+
+
+COMMENT ON COLUMN person.logo IS 'The library file alias of a smaller version of this person''s mugshot.';
+
+
+COMMENT ON COLUMN person.renewal_policy IS 'The policy for membership renewal on this team.';
+
+
+COMMENT ON COLUMN person.personal_standing IS 'The standing of the person, which indicates (for now, just) whether the person can post to a mailing list without requiring first post moderation.  Values are documented in dbschema.PersonalStanding.';
+
+
+COMMENT ON COLUMN person.personal_standing_reason IS 'The reason a person''s standing has changed.';
+
+
+COMMENT ON COLUMN person.mail_resumption_date IS 'A NULL resumption date or a date in the past indicates that there is no vacation in effect.  Vacations are granular to the day, so a datetime is not necessary.';
+
+
+COMMENT ON COLUMN person.mailing_list_auto_subscribe_policy IS 'The auto-subscription policy for the person, i.e. whether and how the user is automatically subscribed to mailing lists for teams they join.  Values are described in dbschema.MailingListAutoSubscribePolicy.';
+
+
+COMMENT ON COLUMN person.mailing_list_receive_duplicates IS 'True means the user wants to receive list copies of messages on which they are explicitly named as a recipient.';
+
+
+COMMENT ON COLUMN person.visibility IS 'person.PersonVisibility enumeration which can be set to Public, Public with Private Membership, or Private.';
+
+
+COMMENT ON COLUMN person.verbose_bugnotifications IS 'If true, all bugnotifications sent to this Person will include the bug description.';
+
+
+COMMENT ON COLUMN person.account IS 'The Account linked to this Person, if there is one.';
+
+
 CREATE SEQUENCE person_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE person_id_seq OWNED BY person.id;
 
+
 CREATE TABLE personlanguage (
     id integer NOT NULL,
     person integer NOT NULL,
@@ -3527,14 +11894,27 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE personlanguage IS 'PersonLanguage: This table stores the preferred languages that a Person has, it''s used in Rosetta to select the languages that should be showed to be translated.';
+
+
+COMMENT ON COLUMN personlanguage.person IS 'This field is a reference to a Person object that has this preference.';
+
+
+COMMENT ON COLUMN personlanguage.language IS 'This field is a reference to a Language object that says that the Person associated to this row knows how to translate/understand this language.';
+
+
 CREATE SEQUENCE personlanguage_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE personlanguage_id_seq OWNED BY personlanguage.id;
 
+
 CREATE TABLE personlocation (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -3549,14 +11929,42 @@
     CONSTRAINT latitude_and_longitude_together CHECK (((latitude IS NULL) = (longitude IS NULL)))
 );
 
+
+COMMENT ON TABLE personlocation IS 'The geographical coordinates and time zone for a person.';
+
+
+COMMENT ON COLUMN personlocation.latitude IS 'The latitude this person has given for their default location.';
+
+
+COMMENT ON COLUMN personlocation.longitude IS 'The longitude this person has given for their default location.';
+
+
+COMMENT ON COLUMN personlocation.time_zone IS 'The name of the time zone this person prefers (if unset, UTC is used).  UI should display dates and times in this time zone wherever possible.';
+
+
+COMMENT ON COLUMN personlocation.last_modified_by IS 'The person who last updated this record. We allow people to provide location and time zone information for other users, when those users have not specified their own location. This allows people to garden the location information for their teams, for example, like a wiki.';
+
+
+COMMENT ON COLUMN personlocation.date_last_modified IS 'The date this record was last modified.';
+
+
+COMMENT ON COLUMN personlocation.visible IS 'Should this person''s location and time zone be visible to others?';
+
+
+COMMENT ON COLUMN personlocation.locked IS 'Whether or not this record can be modified by someone other than the person himself?';
+
+
 CREATE SEQUENCE personlocation_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE personlocation_id_seq OWNED BY personlocation.id;
 
+
 CREATE TABLE personnotification (
     id integer NOT NULL,
     person integer NOT NULL,
@@ -3566,14 +11974,78 @@
     subject text NOT NULL
 );
 
+
+COMMENT ON TABLE personnotification IS 'Notifications to be sent that are related to edits and changes of the details of a specific person or team. Note that these are not keyed against the "person who will be notified", these are notifications "about a person". We use this table to queue up notifications that can then be sent asyncronously - when one user edits information about another person (like the PersonLocation) we want to notify the person concerned that their details have been modified but we do not want to do this during the handling of the form submission. So we store the reminder to notify here, and send it later in a batch. This is modelled on the pattern of BugNotification.';
+
+
+COMMENT ON COLUMN personnotification.person IS 'The Person who has been edited or modified.';
+
+
+COMMENT ON COLUMN personnotification.date_emailed IS 'When this notification was emailed to the relevant people.';
+
+
+COMMENT ON COLUMN personnotification.body IS 'The textual body of the notification to be sent.';
+
+
+COMMENT ON COLUMN personnotification.subject IS 'The subject of the mail to be sent.';
+
+
 CREATE SEQUENCE personnotification_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE personnotification_id_seq OWNED BY personnotification.id;
 
+
+CREATE TABLE personsettings (
+    person integer NOT NULL,
+    selfgenerated_bugnotifications boolean DEFAULT false NOT NULL
+);
+
+
+CREATE TABLE persontransferjob (
+    id integer NOT NULL,
+    job integer NOT NULL,
+    job_type integer NOT NULL,
+    minor_person integer NOT NULL,
+    major_person integer NOT NULL,
+    json_data text
+);
+
+
+COMMENT ON TABLE persontransferjob IS 'Contains references to jobs for adding team members or merging person entries.';
+
+
+COMMENT ON COLUMN persontransferjob.job IS 'A reference to a row in the Job table that has all the common job details.';
+
+
+COMMENT ON COLUMN persontransferjob.job_type IS 'The type of job, like add-member notification or merge persons.';
+
+
+COMMENT ON COLUMN persontransferjob.minor_person IS 'The person that is being added is a new member or being merged into another person.';
+
+
+COMMENT ON COLUMN persontransferjob.major_person IS 'The team receiving a new member or the person that another person is merged into.';
+
+
+COMMENT ON COLUMN persontransferjob.json_data IS 'Data that is specific to the type of job, normally stores text to append to email notifications.';
+
+
+CREATE SEQUENCE persontransferjob_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE persontransferjob_id_seq OWNED BY persontransferjob.id;
+
+
 CREATE TABLE pillarname (
     id integer NOT NULL,
     name text NOT NULL,
@@ -3586,14 +12058,24 @@
     CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
+
+COMMENT ON TABLE pillarname IS 'A cache of the names of our "Pillar''s" (distribution, product, project) to ensure uniqueness in this shared namespace. This is a materialized view maintained by database triggers.';
+
+
+COMMENT ON COLUMN pillarname.alias_for IS 'An alias for another pillarname. Rows with this column set are not maintained by triggers.';
+
+
 CREATE SEQUENCE pillarname_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE pillarname_id_seq OWNED BY pillarname.id;
 
+
 CREATE TABLE pocketchroot (
     id integer NOT NULL,
     distroarchseries integer,
@@ -3602,32 +12084,29 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE pocketchroot IS 'PocketChroots: Which chroot belongs to which pocket of which distroarchseries. Any given pocket of any given distroarchseries needs a specific chroot in order to be built. This table links it all together.';
+
+
+COMMENT ON COLUMN pocketchroot.distroarchseries IS 'Which distroarchseries this chroot applies to.';
+
+
+COMMENT ON COLUMN pocketchroot.pocket IS 'Which pocket of the distroarchseries this chroot applies to. Valid values are specified in dbschema.PackagePublishingPocket';
+
+
+COMMENT ON COLUMN pocketchroot.chroot IS 'The chroot used by the pocket of the distroarchseries.';
+
+
 CREATE SEQUENCE pocketchroot_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE pocketchroot_id_seq OWNED BY pocketchroot.id;
 
-CREATE TABLE pocomment (
-    id integer NOT NULL,
-    potemplate integer NOT NULL,
-    pomsgid integer,
-    language integer,
-    potranslation integer,
-    commenttext text NOT NULL,
-    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
-    person integer
-);
-
-CREATE SEQUENCE pocomment_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE pocomment_id_seq OWNED BY pocomment.id;
 
 CREATE TABLE poexportrequest (
     id integer NOT NULL,
@@ -3638,14 +12117,34 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE poexportrequest IS 'A request from a user that a PO template or a PO file be exported
+asynchronously.';
+
+
+COMMENT ON COLUMN poexportrequest.person IS 'The person who made the request.';
+
+
+COMMENT ON COLUMN poexportrequest.potemplate IS 'The PO template being requested.';
+
+
+COMMENT ON COLUMN poexportrequest.pofile IS 'The PO file being requested, or NULL.';
+
+
+COMMENT ON COLUMN poexportrequest.format IS 'The format the user would like the export to be in. See the RosettaFileFormat DB schema for possible values.';
+
+
 CREATE SEQUENCE poexportrequest_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE poexportrequest_id_seq OWNED BY poexportrequest.id;
 
+
 CREATE TABLE pofile (
     id integer NOT NULL,
     potemplate integer NOT NULL,
@@ -3660,23 +12159,44 @@
     rosettacount integer NOT NULL,
     lastparsed timestamp without time zone,
     owner integer NOT NULL,
-    variant text,
     path text NOT NULL,
     datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
     from_sourcepackagename integer,
     unreviewed_count integer DEFAULT 0 NOT NULL,
-    date_changed timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    CONSTRAINT valid_variant CHECK ((variant <> ''::text))
+    date_changed timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE pofile IS 'This table stores a PO file for a given PO template.';
+
+
+COMMENT ON COLUMN pofile.path IS 'The path (included the filename) inside the tree from where the content was imported.';
+
+
+COMMENT ON COLUMN pofile.from_sourcepackagename IS 'The sourcepackagename from where the last .po file came (only if it''s different from POFile.potemplate.sourcepackagename)';
+
+
+COMMENT ON COLUMN pofile.unreviewed_count IS 'Number of POTMsgSets with new,
+unreviewed TranslationMessages for this POFile.';
+
+
 CREATE SEQUENCE pofile_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE pofile_id_seq OWNED BY pofile.id;
 
+
+CREATE TABLE pofilestatsjob (
+    job integer NOT NULL,
+    pofile integer NOT NULL
+);
+
+
 CREATE TABLE pofiletranslator (
     id integer NOT NULL,
     person integer NOT NULL,
@@ -3685,14 +12205,35 @@
     date_last_touched timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE pofiletranslator IS 'A materialized view caching who has translated what pofile.';
+
+
+COMMENT ON COLUMN pofiletranslator.person IS 'The person who submitted the translation.';
+
+
+COMMENT ON COLUMN pofiletranslator.pofile IS 'The pofile the translation was submitted for.';
+
+
+COMMENT ON COLUMN pofiletranslator.latest_message IS 'Latest translation
+message added to the translation file.';
+
+
+COMMENT ON COLUMN pofiletranslator.date_last_touched IS 'When was added latest
+translation message.';
+
+
 CREATE SEQUENCE pofiletranslator_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE pofiletranslator_id_seq OWNED BY pofiletranslator.id;
 
+
 CREATE TABLE poll (
     id integer NOT NULL,
     team integer NOT NULL,
@@ -3708,14 +12249,48 @@
     CONSTRAINT sane_dates CHECK ((dateopens < datecloses))
 );
 
+
+COMMENT ON TABLE poll IS 'The polls belonging to teams.';
+
+
+COMMENT ON COLUMN poll.team IS 'The team this poll belongs to';
+
+
+COMMENT ON COLUMN poll.name IS 'The unique name of this poll.';
+
+
+COMMENT ON COLUMN poll.title IS 'The title of this poll.';
+
+
+COMMENT ON COLUMN poll.dateopens IS 'The date and time when this poll opens.';
+
+
+COMMENT ON COLUMN poll.datecloses IS 'The date and time when this poll closes.';
+
+
+COMMENT ON COLUMN poll.proposition IS 'The proposition that is going to be voted.';
+
+
+COMMENT ON COLUMN poll.type IS 'The type of this poll (Simple, Preferential, etc).';
+
+
+COMMENT ON COLUMN poll.allowspoilt IS 'If people can spoil their votes.';
+
+
+COMMENT ON COLUMN poll.secrecy IS 'If people votes are SECRET (no one can see), ADMIN (team administrators can see) or PUBLIC (everyone can see).';
+
+
 CREATE SEQUENCE poll_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE poll_id_seq OWNED BY poll.id;
 
+
 CREATE TABLE polloption (
     id integer NOT NULL,
     poll integer NOT NULL,
@@ -3725,44 +12300,49 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE polloption IS 'The options belonging to polls.';
+
+
+COMMENT ON COLUMN polloption.poll IS 'The poll this options belongs to.';
+
+
+COMMENT ON COLUMN polloption.name IS 'The name of this option.';
+
+
+COMMENT ON COLUMN polloption.title IS 'A short title for this option.';
+
+
+COMMENT ON COLUMN polloption.active IS 'If TRUE, people will be able to vote on this option. Otherwise they don''t.';
+
+
 CREATE SEQUENCE polloption_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE polloption_id_seq OWNED BY polloption.id;
 
+
 CREATE TABLE pomsgid (
     id integer NOT NULL,
     msgid text NOT NULL
 );
 
+
 CREATE SEQUENCE pomsgid_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE pomsgid_id_seq OWNED BY pomsgid.id;
 
-CREATE TABLE posubscription (
-    id integer NOT NULL,
-    person integer NOT NULL,
-    potemplate integer NOT NULL,
-    language integer,
-    notificationinterval interval,
-    lastnotified timestamp without time zone,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
-);
-
-CREATE SEQUENCE posubscription_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE posubscription_id_seq OWNED BY posubscription.id;
 
 CREATE TABLE potemplate (
     id integer NOT NULL,
@@ -3792,18 +12372,57 @@
     CONSTRAINT valid_link CHECK ((((productseries IS NULL) <> (distroseries IS NULL)) AND ((distroseries IS NULL) = (sourcepackagename IS NULL))))
 );
 
+
+COMMENT ON TABLE potemplate IS 'This table stores a pot file for a given product.';
+
+
+COMMENT ON COLUMN potemplate.path IS 'The path to the .pot source file inside the tarball tree, including the filename.';
+
+
+COMMENT ON COLUMN potemplate.sourcepackagename IS 'A reference to a sourcepackage name from where this POTemplate comes.';
+
+
+COMMENT ON COLUMN potemplate.distroseries IS 'A reference to the distribution from where this POTemplate comes.';
+
+
+COMMENT ON COLUMN potemplate.sourcepackageversion IS 'The sourcepackage version string from where this potemplate was imported last time with our buildd <-> Rosetta gateway.';
+
+
+COMMENT ON COLUMN potemplate.header IS 'The header of a .pot file when we import it. Most important info from it is POT-Creation-Date and custom headers.';
+
+
+COMMENT ON COLUMN potemplate.productseries IS 'A reference to a ProductSeries from where this POTemplate comes.';
+
+
+COMMENT ON COLUMN potemplate.from_sourcepackagename IS 'The sourcepackagename from where the last .pot file came (only if it''s different from POTemplate.sourcepackagename)';
+
+
+COMMENT ON COLUMN potemplate.source_file IS 'Reference to Librarian file storing the last uploaded template file.';
+
+
+COMMENT ON COLUMN potemplate.source_file_format IS 'File format for the Librarian file referenced in "source_file" column.';
+
+
+COMMENT ON COLUMN potemplate.name IS 'The name of the POTemplate set. It must be unique';
+
+
+COMMENT ON COLUMN potemplate.translation_domain IS 'The translation domain for this POTemplate';
+
+
 CREATE SEQUENCE potemplate_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE potemplate_id_seq OWNED BY potemplate.id;
 
+
 CREATE TABLE potmsgset (
     id integer NOT NULL,
     msgid_singular integer NOT NULL,
-    sequence integer,
     potemplate integer,
     commenttext text,
     filereferences text,
@@ -3813,6 +12432,36 @@
     msgid_plural integer
 );
 
+
+COMMENT ON TABLE potmsgset IS 'This table is stores a collection of msgids
+without their translations and all kind of information associated to that set
+of messages that could be found in a potemplate file.';
+
+
+COMMENT ON COLUMN potmsgset.msgid_singular IS 'The singular msgid for this message.';
+
+
+COMMENT ON COLUMN potmsgset.potemplate IS 'The potemplate where this message set is stored.';
+
+
+COMMENT ON COLUMN potmsgset.commenttext IS 'The comment text that is associated to this message set.';
+
+
+COMMENT ON COLUMN potmsgset.filereferences IS 'The list of files and their line number where this message set was extracted from.';
+
+
+COMMENT ON COLUMN potmsgset.sourcecomment IS 'The comment that was extracted from the source code.';
+
+
+COMMENT ON COLUMN potmsgset.flagscomment IS 'The flags associated with this set (like c-format).';
+
+
+COMMENT ON COLUMN potmsgset.context IS 'Context uniquely defining a message when there are messages with same primemsgids.';
+
+
+COMMENT ON COLUMN potmsgset.msgid_plural IS 'The plural msgid for this message.';
+
+
 CREATE TABLE translationtemplateitem (
     id integer NOT NULL,
     potemplate integer NOT NULL,
@@ -3821,30 +12470,40 @@
     CONSTRAINT translationtemplateitem_sequence_check CHECK ((sequence >= 0))
 );
 
+
 CREATE VIEW potexport AS
     SELECT COALESCE((potmsgset.id)::text, 'X'::text) AS id, potemplate.productseries, potemplate.sourcepackagename, potemplate.distroseries, potemplate.id AS potemplate, potemplate.header AS template_header, potemplate.languagepack, translationtemplateitem.sequence, potmsgset.id AS potmsgset, potmsgset.commenttext AS comment, potmsgset.sourcecomment AS source_comment, potmsgset.filereferences AS file_references, potmsgset.flagscomment AS flags_comment, potmsgset.context, msgid_singular.msgid AS msgid_singular, msgid_plural.msgid AS msgid_plural FROM ((((potmsgset JOIN translationtemplateitem ON ((translationtemplateitem.potmsgset = potmsgset.id))) JOIN potemplate ON ((potemplate.id = translationtemplateitem.potemplate))) LEFT JOIN pomsgid msgid_singular ON ((potmsgset.msgid_singular = msgid_singular.id))) LEFT JOIN pomsgid msgid_plural ON ((potmsgset.msgid_plural = msgid_plural.id)));
 
+
 CREATE SEQUENCE potmsgset_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE potmsgset_id_seq OWNED BY potmsgset.id;
 
+
 CREATE TABLE potranslation (
     id integer NOT NULL,
     translation text NOT NULL
-);
+)
+WITH (fillfactor=100);
+
 
 CREATE SEQUENCE potranslation_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE potranslation_id_seq OWNED BY potranslation.id;
 
+
 CREATE TABLE previewdiff (
     id integer NOT NULL,
     source_revision_id text NOT NULL,
@@ -3854,14 +12513,36 @@
     conflicts text
 );
 
+
+COMMENT ON TABLE previewdiff IS 'Contains information about preview diffs, without duplicating information with BranchMergeProposal.';
+
+
+COMMENT ON COLUMN previewdiff.source_revision_id IS 'The source branch revision_id used to generate this diff.';
+
+
+COMMENT ON COLUMN previewdiff.target_revision_id IS 'The target branch revision_id used to generate this diff.';
+
+
+COMMENT ON COLUMN previewdiff.dependent_revision_id IS 'The dependant branch revision_id used to generate this diff.';
+
+
+COMMENT ON COLUMN previewdiff.diff IS 'The last Diff generated for this PreviewDiff.';
+
+
+COMMENT ON COLUMN previewdiff.conflicts IS 'The text description of any conflicts present.';
+
+
 CREATE SEQUENCE previewdiff_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE previewdiff_id_seq OWNED BY previewdiff.id;
 
+
 CREATE TABLE processor (
     id integer NOT NULL,
     family integer NOT NULL,
@@ -3870,14 +12551,27 @@
     description text NOT NULL
 );
 
+
+COMMENT ON TABLE processor IS 'A single processor for which code might be compiled. For example, i386, P2, P3, P4, Itanium1, Itanium2... each processor belongs to a ProcessorFamily, and it might be that a package is compiled several times for a given Family, with different optimisation settings for each processor.';
+
+
+COMMENT ON COLUMN processor.family IS 'The ProcessorFamily for this Processor.';
+
+
+COMMENT ON COLUMN processor.name IS 'The name of this processor, for example, i386, Pentium, P2, P3, P4, Itanium, Itanium2, K7, Athlon, Opteron... it should be short and unique.';
+
+
 CREATE SEQUENCE processor_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE processor_id_seq OWNED BY processor.id;
 
+
 CREATE TABLE processorfamily (
     id integer NOT NULL,
     name text NOT NULL,
@@ -3886,53 +12580,224 @@
     restricted boolean DEFAULT false NOT NULL
 );
 
+
+COMMENT ON TABLE processorfamily IS 'An architecture, that might consist of several actual processors. Different distributions call these architectures different things, so we have an "architecturetag" in DistroArchSeries that might be different to the architecture''s name.';
+
+
+COMMENT ON COLUMN processorfamily.name IS 'The name of the architecture. This is a short unix-style name such as i386 or amd64';
+
+
+COMMENT ON COLUMN processorfamily.title IS 'A title for the architecture. For example "Intel i386 Compatible".';
+
+
+COMMENT ON COLUMN processorfamily.description IS 'A description for this processor family. It might include any gotchas such as the fact that i386 does not necessarily mean that code would run on a 386... Ubuntu for example requires a 486.';
+
+
 CREATE SEQUENCE processorfamily_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE processorfamily_id_seq OWNED BY processorfamily.id;
 
+
+CREATE TABLE product (
+    id integer NOT NULL,
+    project integer,
+    owner integer NOT NULL,
+    name text NOT NULL,
+    displayname text NOT NULL,
+    title text NOT NULL,
+    summary text NOT NULL,
+    description text,
+    datecreated timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
+    homepageurl text,
+    screenshotsurl text,
+    wikiurl text,
+    listurl text,
+    programminglang text,
+    downloadurl text,
+    lastdoap text,
+    sourceforgeproject text,
+    freshmeatproject text,
+    reviewed boolean DEFAULT false NOT NULL,
+    active boolean DEFAULT true NOT NULL,
+    fti ts2.tsvector,
+    autoupdate boolean DEFAULT false NOT NULL,
+    translationgroup integer,
+    translationpermission integer DEFAULT 1 NOT NULL,
+    official_rosetta boolean DEFAULT false NOT NULL,
+    official_malone boolean DEFAULT false NOT NULL,
+    bug_supervisor integer,
+    security_contact integer,
+    driver integer,
+    bugtracker integer,
+    development_focus integer,
+    homepage_content text,
+    icon integer,
+    mugshot integer,
+    logo integer,
+    official_answers boolean DEFAULT false NOT NULL,
+    private_bugs boolean DEFAULT false NOT NULL,
+    private_specs boolean DEFAULT false NOT NULL,
+    license_info text,
+    official_blueprints boolean DEFAULT false NOT NULL,
+    enable_bug_expiration boolean DEFAULT false NOT NULL,
+    bug_reporting_guidelines text,
+    reviewer_whiteboard text,
+    license_approved boolean DEFAULT false NOT NULL,
+    registrant integer NOT NULL,
+    remote_product text,
+    translation_focus integer,
+    max_bug_heat integer,
+    date_next_suggest_packaging timestamp without time zone,
+    bug_reported_acknowledgement text,
+    answers_usage integer DEFAULT 10 NOT NULL,
+    blueprints_usage integer DEFAULT 10 NOT NULL,
+    translations_usage integer DEFAULT 10 NOT NULL,
+    enable_bugfiling_duplicate_search boolean DEFAULT true NOT NULL,
+    CONSTRAINT only_launchpad_has_expiration CHECK (((enable_bug_expiration IS FALSE) OR (official_malone IS TRUE))),
+    CONSTRAINT private_bugs_need_contact CHECK (((private_bugs IS FALSE) OR (bug_supervisor IS NOT NULL))),
+    CONSTRAINT valid_name CHECK (valid_name(name))
+);
+
+
+COMMENT ON TABLE product IS 'Product: a DOAP Product. This table stores core information about an open source product. In Launchpad, anything that can be shipped as a tarball would be a product, and in some cases there might be products for things that never actually ship, depending on the project. For example, most projects will have a ''website'' product, because that allows you to file a Malone bug against the project website. Note that these are not actual product releases, which are stored in the ProductRelease table.';
+
+
+COMMENT ON COLUMN product.project IS 'Every Product belongs to one and only one Project, which is referenced in this column.';
+
+
+COMMENT ON COLUMN product.owner IS 'The Product owner would typically be the person who created this product in Launchpad. But we will encourage the upstream maintainer of a product to become the owner in Launchpad. The Product owner can edit any aspect of the Product, as well as appointing people to specific roles with regard to the Product. Also, the owner can add a new ProductRelease and also edit Rosetta POTemplates associated with this product.';
+
+
+COMMENT ON COLUMN product.summary IS 'A brief summary of the product. This will be displayed in bold at the top of the product page, above the description.';
+
+
+COMMENT ON COLUMN product.description IS 'A detailed description of the product, highlighting primary features of the product that may be of interest to end-users. The description may also include links and other references to useful information on the web about this product. The description will be displayed on the product page, below the product summary.';
+
+
+COMMENT ON COLUMN product.listurl IS 'This is the URL where information about a mailing list for this Product can be found. The URL might point at a web archive or at the page where one can subscribe to the mailing list.';
+
+
+COMMENT ON COLUMN product.programminglang IS 'This field records, in plain text, the name of any significant programming languages used in this product. There are no rules, conventions or restrictions on this field at present, other than basic sanity. Examples might be "Python", "Python, C" and "Java".';
+
+
+COMMENT ON COLUMN product.downloadurl IS 'The download URL for a Product should be the best place to download that product, typically off the relevant Project web site. This should not point at the actual file, but at a web page with download information.';
+
+
+COMMENT ON COLUMN product.lastdoap IS 'This column stores a cached copy of the last DOAP description we saw for this product. See the Project.lastdoap field for more info.';
+
+
+COMMENT ON COLUMN product.sourceforgeproject IS 'The SourceForge project name for this product. This is not unique as SourceForge doesn''t use the same project/product structure as DOAP.';
+
+
+COMMENT ON COLUMN product.freshmeatproject IS 'The FreshMeat project name for this product. This is not unique as FreshMeat does not have the same project/product structure as DOAP';
+
+
+COMMENT ON COLUMN product.reviewed IS 'Whether or not someone at Canonical has reviewed this product.';
+
+
+COMMENT ON COLUMN product.active IS 'Whether or not this product should be considered active.';
+
+
+COMMENT ON COLUMN product.translationgroup IS 'The TranslationGroup that is responsible for translations for this product. Note that the Product may be part of a Project which also has a TranslationGroup, in which case the translators from both the product and project translation group have permission to edit the translations of this product.';
+
+
+COMMENT ON COLUMN product.translationpermission IS 'The level of openness of this product''s translation process. The enum lists different approaches to translation, from the very open (anybody can edit any translation in any language) to the completely closed (only designated translators can make any changes at all).';
+
+
+COMMENT ON COLUMN product.official_rosetta IS 'Whether or not this product upstream uses Rosetta for its official translation team and coordination. This is a useful indicator in terms of whether translations in Rosetta for this upstream will quickly move upstream.';
+
+
+COMMENT ON COLUMN product.official_malone IS 'Whether or not this product upstream uses Malone for an official bug tracker. This is useful to help indicate whether or not people are likely to pick up on bugs registered in Malone.';
+
+
+COMMENT ON COLUMN product.bug_supervisor IS 'Person who is responsible for managing bugs on this product.';
+
+
+COMMENT ON COLUMN product.security_contact IS 'The person or team who handles security-related issues in the product.';
+
+
+COMMENT ON COLUMN product.driver IS 'This is a driver for the overall product. This driver will be able to approve nominations of bugs and specs to any series in the product, including backporting to old stable series. You want the smallest group of "overall drivers" here, because you can add specific drivers to each series individually.';
+
+
+COMMENT ON COLUMN product.development_focus IS 'The product series that is the current focus of development.';
+
+
+COMMENT ON COLUMN product.homepage_content IS 'A home page for this product in the Launchpad.';
+
+
+COMMENT ON COLUMN product.icon IS 'The library file alias to a small image to be used as an icon whenever we are referring to a product.';
+
+
+COMMENT ON COLUMN product.mugshot IS 'The library file alias of a mugshot image to display as the branding of a product, on its home page.';
+
+
+COMMENT ON COLUMN product.logo IS 'The library file alias of a smaller version of this product''s mugshot.';
+
+
+COMMENT ON COLUMN product.official_answers IS 'Whether or not this product upstream uses Answers officialy. This is useful to help indicate whether or not that a question will receive an answer.';
+
+
+COMMENT ON COLUMN product.private_bugs IS 'Indicates whether bugs filed in this product are automatically marked as private.';
+
+
+COMMENT ON COLUMN product.private_specs IS 'Indicates whether specs filed in this product are automatically marked as private.';
+
+
+COMMENT ON COLUMN product.license_info IS 'Additional information about licenses that are not included in the License enumeration.';
+
+
+COMMENT ON COLUMN product.official_blueprints IS 'Whether or not this product upstream uses Blueprints officially. This is useful to help indicate whether or not the upstream project will be actively watching the blueprints in Launchpad.';
+
+
+COMMENT ON COLUMN product.enable_bug_expiration IS 'Indicates whether automatic bug expiration is enabled.';
+
+
+COMMENT ON COLUMN product.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on this product.';
+
+
+COMMENT ON COLUMN product.reviewer_whiteboard IS 'A whiteboard for Launchpad admins, registry experts and the project owners to capture the state of current issues with the project.';
+
+
+COMMENT ON COLUMN product.license_approved IS 'The Other/Open Source license has been approved by an administrator.';
+
+
+COMMENT ON COLUMN product.registrant IS 'The Product registrant is the Person who created the product in Launchpad.  It is set at creation and is never changed thereafter.';
+
+
+COMMENT ON COLUMN product.remote_product IS 'The ID of this product on its remote bug tracker.';
+
+
+COMMENT ON COLUMN product.translation_focus IS 'The ProductSeries that should get the translation effort focus.';
+
+
+COMMENT ON COLUMN product.max_bug_heat IS 'The highest heat value across bugs for this product.';
+
+
+COMMENT ON COLUMN product.date_next_suggest_packaging IS 'The date when Launchpad can resume suggesting Ubuntu packages that the project provides.';
+
+
+COMMENT ON COLUMN product.bug_reported_acknowledgement IS 'A message of acknowledgement to display to a bug reporter after they''ve reported a new bug.';
+
+
+COMMENT ON COLUMN product.enable_bugfiling_duplicate_search IS 'Enable/disable a search for posiible duplicates when a bug is filed.';
+
+
 CREATE SEQUENCE product_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE product_id_seq OWNED BY product.id;
 
-CREATE TABLE productbounty (
-    id integer NOT NULL,
-    bounty integer NOT NULL,
-    product integer NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
-);
-
-CREATE SEQUENCE productbounty_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE productbounty_id_seq OWNED BY productbounty.id;
-
-CREATE TABLE productcvsmodule (
-    id integer NOT NULL,
-    product integer NOT NULL,
-    anonroot text NOT NULL,
-    module text NOT NULL,
-    weburl text,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
-);
-
-CREATE SEQUENCE productcvsmodule_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE productcvsmodule_id_seq OWNED BY productcvsmodule.id;
 
 CREATE TABLE productlicense (
     id integer NOT NULL,
@@ -3940,14 +12805,27 @@
     license integer NOT NULL
 );
 
+
+COMMENT ON TABLE productlicense IS 'The licenses that cover the software for a product.';
+
+
+COMMENT ON COLUMN productlicense.product IS 'Foreign key to the product that has licenses associated with it.';
+
+
+COMMENT ON COLUMN productlicense.license IS 'An integer referencing a value in the License enumeration in product.py';
+
+
 CREATE SEQUENCE productlicense_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE productlicense_id_seq OWNED BY productlicense.id;
 
+
 CREATE TABLE productrelease (
     id integer NOT NULL,
     datereleased timestamp without time zone NOT NULL,
@@ -3958,14 +12836,39 @@
     milestone integer NOT NULL
 );
 
+
+COMMENT ON TABLE productrelease IS 'A Product Release. This is table stores information about a specific ''upstream'' software release, like Apache 2.0.49 or Evolution 1.5.4.';
+
+
+COMMENT ON COLUMN productrelease.datereleased IS 'The date when this version of the product was released.';
+
+
+COMMENT ON COLUMN productrelease.release_notes IS 'Description of changes in this product release.';
+
+
+COMMENT ON COLUMN productrelease.changelog IS 'Detailed description of changes in this product release.';
+
+
+COMMENT ON COLUMN productrelease.owner IS 'The person who created this product release.';
+
+
+COMMENT ON COLUMN productrelease.datecreated IS 'The timestamp when this product release was created.';
+
+
+COMMENT ON COLUMN productrelease.milestone IS 'The milestone for this product release. This is scheduled to become a NOT NULL column, so every product release will be linked to a unique milestone.';
+
+
 CREATE SEQUENCE productrelease_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE productrelease_id_seq OWNED BY productrelease.id;
 
+
 CREATE TABLE productreleasefile (
     productrelease integer NOT NULL,
     libraryfile integer NOT NULL,
@@ -3978,14 +12881,42 @@
     signature integer
 );
 
+
+COMMENT ON TABLE productreleasefile IS 'Links a ProductRelease to one or more files in the Librarian.';
+
+
+COMMENT ON COLUMN productreleasefile.productrelease IS 'This is the product release this file is associated with';
+
+
+COMMENT ON COLUMN productreleasefile.libraryfile IS 'This is the librarian entry';
+
+
+COMMENT ON COLUMN productreleasefile.filetype IS 'An enum of what kind of file this is. Code tarballs are marked for special treatment (importing into bzr)';
+
+
+COMMENT ON COLUMN productreleasefile.description IS 'A description of what the file contains';
+
+
+COMMENT ON COLUMN productreleasefile.uploader IS 'The person who uploaded this file.';
+
+
+COMMENT ON COLUMN productreleasefile.date_uploaded IS 'The date this file was uploaded.';
+
+
+COMMENT ON COLUMN productreleasefile.signature IS 'This is the signature of the librarian entry as uploaded by the user.';
+
+
 CREATE SEQUENCE productreleasefile_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE productreleasefile_id_seq OWNED BY productreleasefile.id;
 
+
 CREATE TABLE productseries (
     id integer NOT NULL,
     product integer NOT NULL,
@@ -4004,43 +12935,55 @@
     CONSTRAINT valid_releasefileglob CHECK (valid_absolute_url(releasefileglob))
 );
 
+
+COMMENT ON TABLE productseries IS 'A ProductSeries is a set of product releases that are related to a specific version of the product. Typically, each major release of the product starts a new ProductSeries. These often map to a branch in the revision control system of the project, such as "2_0_STABLE". A few conventional Series names are "head" for releases of the HEAD branch, "1.0" for releases with version numbers like "1.0.0" and "1.0.1".  Each product has at least one ProductSeries';
+
+
+COMMENT ON COLUMN productseries.name IS 'The name of the ProductSeries is like a unix name, it should not contain any spaces and should start with a letter or number. Good examples are "2.0", "3.0", "head" and "development".';
+
+
+COMMENT ON COLUMN productseries.summary IS 'A summary of this Product Series. A good example would include the date the series was initiated and whether this is the current recommended series for people to use. The summary is usually displayed at the top of the page, in bold, just beneath the title and above the description, if there is a description field.';
+
+
+COMMENT ON COLUMN productseries.releasefileglob IS 'A fileglob that lets us
+see which URLs are potentially new upstream tarball releases. For example:
+http://ftp.gnu.org/gnu/libtool/libtool-1.5.*.gz.';
+
+
+COMMENT ON COLUMN productseries.releaseverstyle IS 'An enum giving the style
+of this product series release version numbering system.  The options are
+documented in dbschema.UpstreamReleaseVersionStyle.  Most applications use
+Gnu style numbering, but there are other alternatives.';
+
+
+COMMENT ON COLUMN productseries.driver IS 'This is a person or team who can approve spes and bugs for implementation or fixing in this specific series. Note that the product drivers and project drivers can also do this for any series in the product or project, so use this only for the specific team responsible for this specific series.';
+
+
+COMMENT ON COLUMN productseries.status IS 'The current status of this productseries.';
+
+
+COMMENT ON COLUMN productseries.translations_autoimport_mode IS 'Level of
+translations imports from codehosting branch: None, templates only, templates
+and translations. See TranslationsBranchImportMode.';
+
+
+COMMENT ON COLUMN productseries.branch IS 'The branch for this product
+series.';
+
+
+COMMENT ON COLUMN productseries.translations_branch IS 'Branch to push translations updates to.';
+
+
 CREATE SEQUENCE productseries_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE productseries_id_seq OWNED BY productseries.id;
 
-CREATE TABLE productseriescodeimport (
-    id integer NOT NULL,
-    productseries integer NOT NULL,
-    codeimport integer NOT NULL
-);
-
-CREATE SEQUENCE productseriescodeimport_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE productseriescodeimport_id_seq OWNED BY productseriescodeimport.id;
-
-CREATE TABLE productsvnmodule (
-    id integer NOT NULL,
-    product integer NOT NULL,
-    locationurl text NOT NULL,
-    weburl text,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
-);
-
-CREATE SEQUENCE productsvnmodule_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE productsvnmodule_id_seq OWNED BY productsvnmodule.id;
 
 CREATE TABLE project (
     id integer NOT NULL,
@@ -4075,58 +13018,122 @@
     CONSTRAINT valid_name CHECK (valid_name(name))
 );
 
+
+COMMENT ON TABLE project IS 'Project: A DOAP Project. This table is the core of the DOAP section of the Launchpad database. It contains details of a single open source Project and is the anchor point for products, potemplates, and translationefforts.';
+
+
+COMMENT ON COLUMN project.owner IS 'The owner of the project will initially be the person who creates this Project in the system. We will encourage upstream project leaders to take on this role. The Project owner is able to edit the project.';
+
+
+COMMENT ON COLUMN project.name IS 'A short lowercase name uniquely identifying the product. Use cases include being used as a key in URL traversal.';
+
+
+COMMENT ON COLUMN project.summary IS 'A brief summary of this project. This
+will be displayed in bold text just above the description and below the
+title. It should be a single paragraph of not more than 80 words.';
+
+
+COMMENT ON COLUMN project.description IS 'A detailed description of this
+project. This should primarily be focused on the organisational aspects of
+the project, such as the people involved and the structures that the project
+uses to govern itself. It might refer to the primary products of the project
+but the detailed descriptions of those products should be in the
+Product.description field, not here. So, for example, useful information
+such as the dates the project was started and the way the project
+coordinates itself are suitable here.';
+
+
+COMMENT ON COLUMN project.homepageurl IS 'The home page URL of this project. Note that this could well be the home page of the main product of this project as well, if the project is too small to have a separate home page for project and product.';
+
+
+COMMENT ON COLUMN project.wikiurl IS 'This is the URL of a wiki that includes information about the project. It might be a page in a bigger wiki, or it might be the top page of a wiki devoted to this project.';
+
+
+COMMENT ON COLUMN project.lastdoap IS 'This column stores a cached copy of the last DOAP description we saw for this project. We cache the last DOAP fragment for this project because there may be some aspects of it which we are unable to represent in the database (such as multiple homepageurl''s instead of just a single homepageurl) and storing the DOAP file allows us to re-parse it later and recover this information when our database model has been updated appropriately.';
+
+
+COMMENT ON COLUMN project.sourceforgeproject IS 'The SourceForge project name for this project. This is not unique as SourceForge doesn''t use the same project/product structure as DOAP.';
+
+
+COMMENT ON COLUMN project.freshmeatproject IS 'The FreshMeat project name for this project. This is not unique as FreshMeat does not have the same project/product structure as DOAP';
+
+
+COMMENT ON COLUMN project.reviewed IS 'Whether or not someone at Canonical has reviewed this project.';
+
+
+COMMENT ON COLUMN project.active IS 'Whether or not this project should be considered active.';
+
+
+COMMENT ON COLUMN project.translationgroup IS 'The translation group that has permission to edit translations across all products in this project. Note that individual products may have their own translationgroup, in which case those translators will also have permission to edit translations for that product.';
+
+
+COMMENT ON COLUMN project.translationpermission IS 'The level of openness of
+this project''s translation process. The enum lists different approaches to
+translation, from the very open (anybody can edit any translation in any
+language) to the completely closed (only designated translators can make any
+changes at all).';
+
+
+COMMENT ON COLUMN project.driver IS 'This person or team has the ability to approve specs as goals for any series in any product in the project. Similarly, this person or team can approve bugs as targets for fixing in any series, or backporting of fixes to any series.';
+
+
+COMMENT ON COLUMN project.homepage_content IS 'A home page for this project in the Launchpad.';
+
+
+COMMENT ON COLUMN project.icon IS 'The library file alias to a small image to be used as an icon whenever we are referring to a project.';
+
+
+COMMENT ON COLUMN project.mugshot IS 'The library file alias of a mugshot image to display as the branding of a project, on its home page.';
+
+
+COMMENT ON COLUMN project.logo IS 'The library file alias of a smaller version of this product''s mugshot.';
+
+
+COMMENT ON COLUMN project.bug_reporting_guidelines IS 'Guidelines to the end user for reporting bugs on products in this project.';
+
+
+COMMENT ON COLUMN project.reviewer_whiteboard IS 'A whiteboard for Launchpad admins, registry experts and the project owners to capture the state of current issues with the project.';
+
+
+COMMENT ON COLUMN project.registrant IS 'The registrant is the Person who created the project in Launchpad.  It is set at creation and is never changed thereafter.';
+
+
+COMMENT ON COLUMN project.max_bug_heat IS 'The highest heat value across bugs for products in this project.';
+
+
+COMMENT ON COLUMN project.bug_reported_acknowledgement IS 'A message of acknowledgement to display to a bug reporter after they''ve reported a new bug.';
+
+
 CREATE SEQUENCE project_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE project_id_seq OWNED BY project.id;
 
-CREATE TABLE projectbounty (
-    id integer NOT NULL,
-    bounty integer NOT NULL,
-    project integer NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
-);
-
-CREATE SEQUENCE projectbounty_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE projectbounty_id_seq OWNED BY projectbounty.id;
-
-CREATE TABLE projectrelationship (
-    id integer NOT NULL,
-    subject integer NOT NULL,
-    label integer NOT NULL,
-    object integer NOT NULL
-);
-
-CREATE SEQUENCE projectrelationship_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE projectrelationship_id_seq OWNED BY projectrelationship.id;
-
-CREATE TABLE pushmirroraccess (
-    id integer NOT NULL,
-    name text NOT NULL,
-    person integer,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
-);
-
-CREATE SEQUENCE pushmirroraccess_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE pushmirroraccess_id_seq OWNED BY pushmirroraccess.id;
+
+CREATE TABLE publisherconfig (
+    id integer NOT NULL,
+    distribution integer NOT NULL,
+    root_dir text NOT NULL,
+    base_url text NOT NULL,
+    copy_base_url text NOT NULL
+);
+
+
+CREATE SEQUENCE publisherconfig_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE publisherconfig_id_seq OWNED BY publisherconfig.id;
+
 
 CREATE TABLE question (
     id integer NOT NULL,
@@ -4156,14 +13163,66 @@
     CONSTRAINT sourcepackagename_needs_distro CHECK (((sourcepackagename IS NULL) OR (distribution IS NOT NULL)))
 );
 
+
+COMMENT ON TABLE question IS 'A question, or support request, for a distribution or for an application. Such questions are created by end users who need support on a particular feature or package or product.';
+
+
+COMMENT ON COLUMN question.assignee IS 'The person who has been assigned to resolve this question. Note that there is no requirement that every question be assigned somebody. Anybody can chip in to help resolve a question, and if they think they have done so we call them the "answerer".';
+
+
+COMMENT ON COLUMN question.answerer IS 'The person who last claimed to have "solved" this support question, giving a response that the owner believe should be sufficient to close the question. This will move the status of the question to "SOLVED". Note that the only person who can actually set the status to SOLVED is the person who asked the question.';
+
+
+COMMENT ON COLUMN question.product IS 'The upstream product to which this quesiton is related. Note that a quesiton MUST be linked either to a product, or to a distribution.';
+
+
+COMMENT ON COLUMN question.distribution IS 'The distribution for which a question was filed. Note that a request MUST be linked either to a product or a distribution.';
+
+
+COMMENT ON COLUMN question.sourcepackagename IS 'An optional source package name. This only makes sense if the question is bound to a distribution.';
+
+
+COMMENT ON COLUMN question.datelastquery IS 'The date we last saw a comment from the requester (owner).';
+
+
+COMMENT ON COLUMN question.dateaccepted IS 'The date we "confirmed" or "accepted" this question. It is usually set to the date of the first response by someone other than the requester. This allows us to track the time between first request and first response.';
+
+
+COMMENT ON COLUMN question.datedue IS 'The date this question is "due", if such a date can be established. Usually this will be set automatically on the basis of a support contract SLA commitment.';
+
+
+COMMENT ON COLUMN question.datelastresponse IS 'The date we last saw a comment from somebody other than the requester.';
+
+
+COMMENT ON COLUMN question.date_solved IS 'The date this question was last marked as solved by the requester (owner). The requester either found a solution, or accepted an answer from another user.';
+
+
+COMMENT ON COLUMN question.dateclosed IS 'The date the requester marked this question CLOSED.';
+
+
+COMMENT ON COLUMN question.whiteboard IS 'A general status whiteboard. This is a scratch space to which arbitrary data can be added (there is only one constant whiteboard with no history). It is displayed at the top of the question. So its a useful way for projects to add their own semantics or metadata to the Answer Tracker.';
+
+
+COMMENT ON COLUMN question.answer IS 'The QuestionMessage that was accepted by the submitter as the "answer" to the question';
+
+
+COMMENT ON COLUMN question.language IS 'The language of the question''s title and description.';
+
+
+COMMENT ON COLUMN question.faq IS 'The FAQ document that contains the long answer to this question.';
+
+
 CREATE SEQUENCE question_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE question_id_seq OWNED BY question.id;
 
+
 CREATE TABLE questionbug (
     id integer NOT NULL,
     question integer NOT NULL,
@@ -4171,30 +13230,86 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE questionbug IS 'A link between a question and a bug, showing that the bug is somehow related to this question.';
+
+
 CREATE SEQUENCE questionbug_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE questionbug_id_seq OWNED BY questionbug.id;
 
+
+CREATE TABLE questionjob (
+    id integer NOT NULL,
+    job integer NOT NULL,
+    job_type integer NOT NULL,
+    question integer NOT NULL,
+    json_data text
+);
+
+
+COMMENT ON TABLE questionjob IS 'Contains references to jobs regarding questions.';
+
+
+COMMENT ON COLUMN questionjob.job IS 'A reference to a row in the Job table that has all the common job details.';
+
+
+COMMENT ON COLUMN questionjob.job_type IS 'The type of job, such as new-answer-notification.';
+
+
+COMMENT ON COLUMN questionjob.question IS 'The newly added question message.';
+
+
+COMMENT ON COLUMN questionjob.json_data IS 'Data that is specific to the type of job, normally stores text to append to email notifications.';
+
+
+CREATE SEQUENCE questionjob_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE questionjob_id_seq OWNED BY questionjob.id;
+
+
 CREATE TABLE questionmessage (
     id integer NOT NULL,
     question integer NOT NULL,
     message integer NOT NULL,
     action integer NOT NULL,
-    new_status integer NOT NULL
+    new_status integer NOT NULL,
+    owner integer NOT NULL
 );
 
+
+COMMENT ON TABLE questionmessage IS 'A link between a question and a message. This means that the message will be displayed on the question page.';
+
+
+COMMENT ON COLUMN questionmessage.action IS 'The action on the question that was done with this message. This is a value from the QuestionAction enum.';
+
+
+COMMENT ON COLUMN questionmessage.new_status IS 'The status of the question after this message.';
+
+
 CREATE SEQUENCE questionmessage_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE questionmessage_id_seq OWNED BY questionmessage.id;
 
+
 CREATE TABLE questionreopening (
     id integer NOT NULL,
     question integer NOT NULL,
@@ -4205,14 +13320,30 @@
     priorstate integer NOT NULL
 );
 
+
+COMMENT ON TABLE questionreopening IS 'A record of the times when a question was re-opened. In each case we store the time that it happened, the person who did it, and the person who had previously answered / rejected the question.';
+
+
+COMMENT ON COLUMN questionreopening.reopener IS 'The person who reopened the question.';
+
+
+COMMENT ON COLUMN questionreopening.answerer IS 'The person who was previously listed as the answerer of the question.';
+
+
+COMMENT ON COLUMN questionreopening.priorstate IS 'The state of the question before it was reopened. You can reopen a question that is ANSWERED, or CLOSED, or REJECTED.';
+
+
 CREATE SEQUENCE questionreopening_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE questionreopening_id_seq OWNED BY questionreopening.id;
 
+
 CREATE TABLE questionsubscription (
     id integer NOT NULL,
     question integer NOT NULL,
@@ -4220,42 +13351,46 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE questionsubscription IS 'A subscription of a person to a particular question.';
+
+
 CREATE SEQUENCE questionsubscription_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE questionsubscription_id_seq OWNED BY questionsubscription.id;
 
-CREATE TABLE requestedcds (
+
+CREATE TABLE revision (
     id integer NOT NULL,
-    request integer NOT NULL,
-    quantity integer NOT NULL,
-    flavour integer NOT NULL,
-    distroseries integer NOT NULL,
-    architecture integer NOT NULL,
-    quantityapproved integer NOT NULL,
-    CONSTRAINT quantity_is_positive CHECK ((quantity >= 0)),
-    CONSTRAINT quantityapproved_is_positive CHECK ((quantityapproved >= 0))
+    date_created timestamp without time zone DEFAULT timezone('UTC'::text, ('now'::text)::timestamp(6) with time zone) NOT NULL,
+    log_body text NOT NULL,
+    revision_author integer NOT NULL,
+    gpgkey integer,
+    revision_id text NOT NULL,
+    revision_date timestamp without time zone,
+    karma_allocated boolean DEFAULT false
 );
-
-CREATE SEQUENCE requestedcds_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE requestedcds_id_seq OWNED BY requestedcds.id;
+ALTER TABLE ONLY revision ALTER COLUMN revision_author SET STATISTICS 500;
+ALTER TABLE ONLY revision ALTER COLUMN revision_date SET STATISTICS 500;
+
 
 CREATE SEQUENCE revision_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE revision_id_seq OWNED BY revision.id;
 
+
 CREATE TABLE revisionauthor (
     id integer NOT NULL,
     name text NOT NULL,
@@ -4263,14 +13398,30 @@
     person integer
 );
 
+
+COMMENT ON TABLE revisionauthor IS 'All distinct authors for revisions.';
+
+
+COMMENT ON COLUMN revisionauthor.name IS 'The exact text extracted from the branch revision.';
+
+
+COMMENT ON COLUMN revisionauthor.email IS 'A valid email address extracted from the name.  This email address may or may not be associated with a Launchpad user at this stage.';
+
+
+COMMENT ON COLUMN revisionauthor.person IS 'The Launchpad person that has a verified email address that matches the email address of the revision author.';
+
+
 CREATE SEQUENCE revisionauthor_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE revisionauthor_id_seq OWNED BY revisionauthor.id;
 
+
 CREATE TABLE revisioncache (
     id integer NOT NULL,
     revision integer NOT NULL,
@@ -4283,16 +13434,41 @@
     CONSTRAINT valid_target CHECK ((((distroseries IS NULL) = (sourcepackagename IS NULL)) AND (((distroseries IS NULL) AND (product IS NULL)) OR ((distroseries IS NULL) <> (product IS NULL)))))
 );
 
+
+COMMENT ON TABLE revisioncache IS 'A cache of revisions where the revision date is in the last 30 days.';
+
+
+COMMENT ON COLUMN revisioncache.revision IS 'A reference to the actual revision.';
+
+
+COMMENT ON COLUMN revisioncache.revision_author IS 'A refernce to the revision author for the revision.';
+
+
+COMMENT ON COLUMN revisioncache.revision_date IS 'The date the revision was made.  Should be within 30 days of today (or the cleanup code is not cleaning up).';
+
+
+COMMENT ON COLUMN revisioncache.product IS 'The product that the revision is found in (if it is indeed in a particular product).';
+
+
+COMMENT ON COLUMN revisioncache.distroseries IS 'The distroseries for which a source package branch contains the revision.';
+
+
+COMMENT ON COLUMN revisioncache.sourcepackagename IS 'The sourcepackagename for which a source package branch contains the revision.';
+
+
+COMMENT ON COLUMN revisioncache.private IS 'True if the revision is only found in private branches, False if it can be found in a non-private branch.';
+
+
 CREATE SEQUENCE revisioncache_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE revisioncache_id_seq OWNED BY revisioncache.id;
 
-CREATE VIEW revisionnumber AS
-    SELECT branchrevision.id, branchrevision.sequence, branchrevision.branch, branchrevision.revision FROM branchrevision;
 
 CREATE TABLE revisionparent (
     id integer NOT NULL,
@@ -4301,14 +13477,18 @@
     parent_id text NOT NULL
 );
 
+
 CREATE SEQUENCE revisionparent_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE revisionparent_id_seq OWNED BY revisionparent.id;
 
+
 CREATE TABLE revisionproperty (
     id integer NOT NULL,
     revision integer NOT NULL,
@@ -4316,14 +13496,30 @@
     value text NOT NULL
 );
 
+
+COMMENT ON TABLE revisionproperty IS 'A collection of name and value pairs that appear on a revision.';
+
+
+COMMENT ON COLUMN revisionproperty.revision IS 'The revision which has properties.';
+
+
+COMMENT ON COLUMN revisionproperty.name IS 'The name of the property.';
+
+
+COMMENT ON COLUMN revisionproperty.value IS 'The value of the property.';
+
+
 CREATE SEQUENCE revisionproperty_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE revisionproperty_id_seq OWNED BY revisionproperty.id;
 
+
 CREATE TABLE scriptactivity (
     id integer NOT NULL,
     name text NOT NULL,
@@ -4332,27 +13528,56 @@
     date_completed timestamp without time zone NOT NULL
 );
 
+
+COMMENT ON TABLE scriptactivity IS 'Records of successful runs of scripts ';
+
+
+COMMENT ON COLUMN scriptactivity.name IS 'The name of the script';
+
+
+COMMENT ON COLUMN scriptactivity.hostname IS 'The hostname of the machine where the script was run';
+
+
+COMMENT ON COLUMN scriptactivity.date_started IS 'The date at which the script started';
+
+
+COMMENT ON COLUMN scriptactivity.date_completed IS 'The date at which the script completed';
+
+
 CREATE SEQUENCE scriptactivity_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE scriptactivity_id_seq OWNED BY scriptactivity.id;
 
+
 CREATE TABLE section (
     id integer NOT NULL,
     name text NOT NULL
 );
 
+
+COMMENT ON TABLE section IS 'Known sections in Launchpad';
+
+
+COMMENT ON COLUMN section.name IS 'Section name text';
+
+
 CREATE SEQUENCE section_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE section_id_seq OWNED BY section.id;
 
+
 CREATE TABLE sectionselection (
     id integer NOT NULL,
     distroseries integer NOT NULL,
@@ -4360,14 +13585,27 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE sectionselection IS 'Allowed sections in a given distroseries.';
+
+
+COMMENT ON COLUMN sectionselection.distroseries IS 'Refers to the distroseries in question.';
+
+
+COMMENT ON COLUMN sectionselection.section IS 'Refers to the section in question.';
+
+
 CREATE SEQUENCE sectionselection_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sectionselection_id_seq OWNED BY sectionselection.id;
 
+
 CREATE TABLE seriessourcepackagebranch (
     id integer NOT NULL,
     distroseries integer NOT NULL,
@@ -4378,151 +13616,38 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE seriessourcepackagebranch IS 'Link between branches and distribution suite.';
+
+
+COMMENT ON COLUMN seriessourcepackagebranch.distroseries IS 'The distroseries the branch is linked to.';
+
+
+COMMENT ON COLUMN seriessourcepackagebranch.pocket IS 'The pocket the branch is linked to.';
+
+
+COMMENT ON COLUMN seriessourcepackagebranch.sourcepackagename IS 'The sourcepackagename the branch is linked to.';
+
+
+COMMENT ON COLUMN seriessourcepackagebranch.branch IS 'The branch being linked to a distribution suite.';
+
+
+COMMENT ON COLUMN seriessourcepackagebranch.registrant IS 'The person who registered this link.';
+
+
+COMMENT ON COLUMN seriessourcepackagebranch.date_created IS 'The date this link was created.';
+
+
 CREATE SEQUENCE seriessourcepackagebranch_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE seriessourcepackagebranch_id_seq OWNED BY seriessourcepackagebranch.id;
 
-CREATE TABLE shipitreport (
-    id integer NOT NULL,
-    datecreated timestamp without time zone NOT NULL,
-    csvfile integer NOT NULL
-);
-
-CREATE SEQUENCE shipitreport_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE shipitreport_id_seq OWNED BY shipitreport.id;
-
-CREATE TABLE shipitsurvey (
-    id integer NOT NULL,
-    account integer NOT NULL,
-    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    exported boolean DEFAULT false NOT NULL
-);
-
-CREATE SEQUENCE shipitsurvey_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE shipitsurvey_id_seq OWNED BY shipitsurvey.id;
-
-CREATE TABLE shipitsurveyanswer (
-    id integer NOT NULL,
-    answer text NOT NULL
-);
-
-CREATE SEQUENCE shipitsurveyanswer_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE shipitsurveyanswer_id_seq OWNED BY shipitsurveyanswer.id;
-
-CREATE TABLE shipitsurveyquestion (
-    id integer NOT NULL,
-    question text NOT NULL
-);
-
-CREATE SEQUENCE shipitsurveyquestion_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE shipitsurveyquestion_id_seq OWNED BY shipitsurveyquestion.id;
-
-CREATE TABLE shipitsurveyresult (
-    id integer NOT NULL,
-    survey integer NOT NULL,
-    question integer NOT NULL,
-    answer integer
-);
-
-CREATE SEQUENCE shipitsurveyresult_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE shipitsurveyresult_id_seq OWNED BY shipitsurveyresult.id;
-
-CREATE TABLE shipment (
-    id integer NOT NULL,
-    logintoken text NOT NULL,
-    shippingrun integer NOT NULL,
-    dateshipped timestamp without time zone,
-    shippingservice integer NOT NULL,
-    trackingcode text
-);
-
-CREATE SEQUENCE shipment_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE shipment_id_seq OWNED BY shipment.id;
-
-CREATE TABLE shippingrequest (
-    id integer NOT NULL,
-    recipient integer NOT NULL,
-    whoapproved integer,
-    whocancelled integer,
-    daterequested timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
-    reason text,
-    highpriority boolean DEFAULT false NOT NULL,
-    recipientdisplayname text NOT NULL,
-    addressline1 text NOT NULL,
-    addressline2 text,
-    organization text,
-    city text NOT NULL,
-    province text,
-    country integer NOT NULL,
-    postcode text,
-    phone text,
-    fti ts2.tsvector,
-    shipment integer,
-    status integer NOT NULL,
-    normalized_address text NOT NULL,
-    type integer,
-    is_admin_request boolean DEFAULT false NOT NULL,
-    CONSTRAINT enforce_shipped_status CHECK (((status <> 4) OR (shipment IS NOT NULL))),
-    CONSTRAINT printable_addresses CHECK (is_printable_ascii((((((((COALESCE(recipientdisplayname, ''::text) || COALESCE(addressline1, ''::text)) || COALESCE(addressline2, ''::text)) || COALESCE(organization, ''::text)) || COALESCE(city, ''::text)) || COALESCE(province, ''::text)) || COALESCE(postcode, ''::text)) || COALESCE(phone, ''::text))))
-);
-
-CREATE SEQUENCE shippingrequest_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE shippingrequest_id_seq OWNED BY shippingrequest.id;
-
-CREATE TABLE shippingrun (
-    id integer NOT NULL,
-    datecreated timestamp without time zone NOT NULL,
-    sentforshipping boolean DEFAULT false NOT NULL,
-    csvfile integer,
-    requests_count integer NOT NULL
-);
-
-CREATE SEQUENCE shippingrun_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE shippingrun_id_seq OWNED BY shippingrun.id;
 
 CREATE TABLE signedcodeofconduct (
     id integer NOT NULL,
@@ -4535,14 +13660,18 @@
     admincomment text
 );
 
+
 CREATE SEQUENCE signedcodeofconduct_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE signedcodeofconduct_id_seq OWNED BY signedcodeofconduct.id;
 
+
 CREATE TABLE sourcepackagepublishinghistory (
     id integer NOT NULL,
     sourcepackagerelease integer NOT NULL,
@@ -4560,9 +13689,64 @@
     pocket integer DEFAULT 0 NOT NULL,
     archive integer NOT NULL,
     removed_by integer,
-    removal_comment text
+    removal_comment text,
+    ancestor integer,
+    sourcepackagename integer,
+    creator integer
 );
 
+
+COMMENT ON TABLE sourcepackagepublishinghistory IS 'SourcePackagePublishingHistory: The history of a SourcePackagePublishing record. This table represents the lifetime of a publishing record from inception to deletion. Records are never removed from here and in time the publishing table may become a view onto this table. A column being NULL indicates there''s no data for that state transition. E.g. a package which is removed without being superseded won''t have datesuperseded or supersededby filled in.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.sourcepackagerelease IS 'The sourcepackagerelease being published.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.distroseries IS 'The distroseries into which the sourcepackagerelease is being published.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.status IS 'The current status of the publishing.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.component IS 'The component into which the publishing takes place.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.section IS 'The section into which the publishing takes place.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.datecreated IS 'The date/time on which the publishing record was created.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.datepublished IS 'The date/time on which the source was actually published into an archive.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.datesuperseded IS 'The date/time on which the source was superseded by a new source.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.supersededby IS 'The source which superseded this one.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.datemadepending IS 'The date/time on which this publishing record was made to be pending removal from the archive.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.scheduleddeletiondate IS 'The date/time at which the source is/was scheduled to be deleted.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.dateremoved IS 'The date/time at which the source was actually deleted.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.pocket IS 'The pocket into which this record is published. The RELEASE pocket (zero) provides behaviour as normal. Other pockets may append things to the distroseries name such as the UPDATES pocket (-updates), the SECURITY pocket (-security) and the PROPOSED pocket (-proposed)';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.archive IS 'The target archive for thi publishing record.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.removed_by IS 'Person responsible for the removal.';
+
+
+COMMENT ON COLUMN sourcepackagepublishinghistory.removal_comment IS 'Reason why the publication was removed.';
+
+
 CREATE TABLE sourcepackagereleasefile (
     sourcepackagerelease integer NOT NULL,
     libraryfile integer NOT NULL,
@@ -4570,39 +13754,75 @@
     id integer DEFAULT nextval(('sourcepackagereleasefile_id_seq'::text)::regclass) NOT NULL
 );
 
+
+COMMENT ON TABLE sourcepackagereleasefile IS 'SourcePackageReleaseFile: A soyuz source package release file. This table links sourcepackagereleasehistory records to the files which comprise the input.';
+
+
+COMMENT ON COLUMN sourcepackagereleasefile.sourcepackagerelease IS 'The sourcepackagerelease that this file belongs to';
+
+
+COMMENT ON COLUMN sourcepackagereleasefile.libraryfile IS 'The libraryfilealias embodying this file';
+
+
+COMMENT ON COLUMN sourcepackagereleasefile.filetype IS 'The type of the file. E.g. TAR, DIFF, DSC';
+
+
 CREATE VIEW sourcepackagefilepublishing AS
     SELECT (((libraryfilealias.id)::text || '.'::text) || (securesourcepackagepublishinghistory.id)::text) AS id, distroseries.distribution, securesourcepackagepublishinghistory.id AS sourcepackagepublishing, sourcepackagereleasefile.libraryfile AS libraryfilealias, libraryfilealias.filename AS libraryfilealiasfilename, sourcepackagename.name AS sourcepackagename, component.name AS componentname, distroseries.name AS distroseriesname, securesourcepackagepublishinghistory.status AS publishingstatus, securesourcepackagepublishinghistory.pocket, securesourcepackagepublishinghistory.archive FROM ((((((sourcepackagepublishinghistory securesourcepackagepublishinghistory JOIN sourcepackagerelease ON ((securesourcepackagepublishinghistory.sourcepackagerelease = sourcepackagerelease.id))) JOIN sourcepackagename ON ((sourcepackagerelease.sourcepackagename = sourcepackagename.id))) JOIN sourcepackagereleasefile ON ((sourcepackagereleasefile.sourcepackagerelease = sourcepackagerelease.id))) JOIN libraryfilealias ON ((libraryfilealias.id = sourcepackagereleasefile.libraryfile))) JOIN distroseries ON ((securesourcepackagepublishinghistory.distroseries = distroseries.id))) JOIN component ON ((securesourcepackagepublishinghistory.component = component.id))) WHERE (securesourcepackagepublishinghistory.dateremoved IS NULL);
 
+
+COMMENT ON VIEW sourcepackagefilepublishing IS 'This view is used mostly by Lucille while performing publishing and unpublishing operations. It lists all the files associated with a sourcepackagerelease and collates all the textual representations needed for publishing components etc to allow rapid queries from SQLObject.';
+
+
 CREATE TABLE sourcepackageformatselection (
     id integer NOT NULL,
     distroseries integer NOT NULL,
     format integer NOT NULL
 );
 
+
+COMMENT ON TABLE sourcepackageformatselection IS 'Allowed source package formats for a given distroseries.';
+
+
+COMMENT ON COLUMN sourcepackageformatselection.distroseries IS 'Refers to the distroseries in question.';
+
+
+COMMENT ON COLUMN sourcepackageformatselection.format IS 'The SourcePackageFormat to allow.';
+
+
 CREATE SEQUENCE sourcepackageformatselection_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackageformatselection_id_seq OWNED BY sourcepackageformatselection.id;
 
+
 CREATE SEQUENCE sourcepackagename_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagename_id_seq OWNED BY sourcepackagename.id;
 
+
 CREATE SEQUENCE sourcepackagepublishinghistory_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagepublishinghistory_id_seq OWNED BY sourcepackagepublishinghistory.id;
 
+
 CREATE TABLE sourcepackagerecipe (
     id integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -4610,51 +13830,108 @@
     registrant integer NOT NULL,
     owner integer NOT NULL,
     name text NOT NULL,
-    description text NOT NULL,
+    description text,
     build_daily boolean DEFAULT false NOT NULL,
     daily_build_archive integer,
     is_stale boolean DEFAULT true NOT NULL
 );
 
+
+COMMENT ON TABLE sourcepackagerecipe IS 'A recipe for assembling a source package from branches.';
+
+
+COMMENT ON COLUMN sourcepackagerecipe.registrant IS 'The person who created this recipe.';
+
+
+COMMENT ON COLUMN sourcepackagerecipe.owner IS 'The person or team who can edit this recipe.';
+
+
+COMMENT ON COLUMN sourcepackagerecipe.name IS 'The name of the recipe in the web/URL.';
+
+
+COMMENT ON COLUMN sourcepackagerecipe.build_daily IS 'If true, this recipe should be built daily.';
+
+
+COMMENT ON COLUMN sourcepackagerecipe.daily_build_archive IS 'The archive to build into for daily builds.';
+
+
+COMMENT ON COLUMN sourcepackagerecipe.is_stale IS 'True if this recipe has not been built since a branch was updated.';
+
+
 CREATE SEQUENCE sourcepackagerecipe_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagerecipe_id_seq OWNED BY sourcepackagerecipe.id;
 
+
 CREATE TABLE sourcepackagerecipebuild (
     id integer NOT NULL,
     distroseries integer NOT NULL,
     requester integer NOT NULL,
-    recipe integer NOT NULL,
+    recipe integer,
     manifest integer,
     package_build integer NOT NULL
 );
 
+
+COMMENT ON TABLE sourcepackagerecipebuild IS 'The build record for the process of building a source package as described by a recipe.';
+
+
+COMMENT ON COLUMN sourcepackagerecipebuild.distroseries IS 'The distroseries the build was for.';
+
+
+COMMENT ON COLUMN sourcepackagerecipebuild.requester IS 'Who requested the build.';
+
+
+COMMENT ON COLUMN sourcepackagerecipebuild.recipe IS 'The recipe being processed.';
+
+
+COMMENT ON COLUMN sourcepackagerecipebuild.manifest IS 'The evaluated recipe that was built.';
+
+
+COMMENT ON COLUMN sourcepackagerecipebuild.package_build IS 'The package_build with the base information about this build.';
+
+
 CREATE SEQUENCE sourcepackagerecipebuild_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagerecipebuild_id_seq OWNED BY sourcepackagerecipebuild.id;
 
+
 CREATE TABLE sourcepackagerecipebuildjob (
     id integer NOT NULL,
     job integer NOT NULL,
-    sourcepackage_recipe_build integer
+    sourcepackage_recipe_build integer NOT NULL
 );
 
+
+COMMENT ON TABLE sourcepackagerecipebuildjob IS 'The link between a SourcePackageRecipeBuild row and a Job row to schedule a build of a source package recipe.';
+
+
+COMMENT ON COLUMN sourcepackagerecipebuildjob.sourcepackage_recipe_build IS 'The build record describing the package being built.';
+
+
 CREATE SEQUENCE sourcepackagerecipebuildjob_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagerecipebuildjob_id_seq OWNED BY sourcepackagerecipebuildjob.id;
 
+
 CREATE TABLE sourcepackagerecipedata (
     id integer NOT NULL,
     base_branch integer NOT NULL,
@@ -4666,14 +13943,39 @@
     CONSTRAINT sourcepackagerecipedata__recipe_or_build_is_not_null CHECK (((sourcepackage_recipe IS NULL) <> (sourcepackage_recipe_build IS NULL)))
 );
 
+
+COMMENT ON TABLE sourcepackagerecipedata IS 'The database representation of a BaseRecipeBranch from bzr-builder.  Exactly one of sourcepackage_recipe or sourcepackage_recipe_build will be non-NULL.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedata.base_branch IS 'The branch the recipe is based on.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedata.recipe_format IS 'The format version of the recipe.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedata.deb_version_template IS 'The template for the revision number of the build.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedata.revspec IS 'The revision from base_branch to use.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedata.sourcepackage_recipe IS 'The recipe that this data is for.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedata.sourcepackage_recipe_build IS 'The build that resulted in this manifest.';
+
+
 CREATE SEQUENCE sourcepackagerecipedata_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagerecipedata_id_seq OWNED BY sourcepackagerecipedata.id;
 
+
 CREATE TABLE sourcepackagerecipedatainstruction (
     id integer NOT NULL,
     name text NOT NULL,
@@ -4685,47 +13987,105 @@
     directory text,
     recipe_data integer NOT NULL,
     parent_instruction integer,
-    CONSTRAINT sourcepackagerecipedatainstruction__directory_not_null CHECK ((((type = 1) AND (directory IS NULL)) OR ((type = 2) AND (directory IS NOT NULL))))
+    source_directory text,
+    CONSTRAINT sourcepackagerecipedatainstruction__directory_not_null CHECK ((((type = 3) OR ((type = 1) AND (directory IS NULL))) OR ((type = 2) AND (directory IS NOT NULL)))),
+    CONSTRAINT sourcepackagerecipedatainstruction__source_directory_null CHECK ((((type = ANY (ARRAY[1, 2])) AND (source_directory IS NULL)) OR ((type = 3) AND (source_directory IS NOT NULL))))
 );
 
+
+COMMENT ON TABLE sourcepackagerecipedatainstruction IS 'A line from the recipe, specifying a branch to nest or merge.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.name IS 'The name of the instruction.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.type IS 'The type of the instruction (MERGE == 1, NEST == 2).';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.comment IS 'The comment from the recipe about this instruction.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.line_number IS 'The line number of the instruction in the recipe.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.branch IS 'The branch being merged or nested.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.revspec IS 'The revision of the branch to use.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.directory IS 'The location to nest at, if this is a nest/nest-part instruction.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.recipe_data IS 'The SourcePackageRecipeData this instruction is part of.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.parent_instruction IS 'The nested branch this instruction applies to, or NULL for a top-level instruction.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedatainstruction.source_directory IS 'The location in the branch to nest, if this is a nest-part instruction.';
+
+
 CREATE SEQUENCE sourcepackagerecipedatainstruction_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagerecipedatainstruction_id_seq OWNED BY sourcepackagerecipedatainstruction.id;
 
+
 CREATE TABLE sourcepackagerecipedistroseries (
     id integer NOT NULL,
     sourcepackagerecipe integer NOT NULL,
     distroseries integer NOT NULL
 );
 
+
+COMMENT ON TABLE sourcepackagerecipedistroseries IS 'Link table for sourcepackagerecipe and distroseries.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedistroseries.sourcepackagerecipe IS 'The primary key of the SourcePackageRecipe.';
+
+
+COMMENT ON COLUMN sourcepackagerecipedistroseries.distroseries IS 'The primary key of the DistroSeries.';
+
+
 CREATE SEQUENCE sourcepackagerecipedistroseries_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagerecipedistroseries_id_seq OWNED BY sourcepackagerecipedistroseries.id;
 
+
 CREATE SEQUENCE sourcepackagerelease_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagerelease_id_seq OWNED BY sourcepackagerelease.id;
 
+
 CREATE SEQUENCE sourcepackagereleasefile_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sourcepackagereleasefile_id_seq OWNED BY sourcepackagereleasefile.id;
 
+
 CREATE TABLE specification (
     id integer NOT NULL,
     name text NOT NULL,
@@ -4774,14 +14134,90 @@
     CONSTRAINT valid_url CHECK (valid_absolute_url(specurl))
 );
 
+
+COMMENT ON TABLE specification IS 'A feature specification. At the moment we do not store the actual specification, we store a URL for the spec, which is managed in a wiki somewhere else. We store the overall state of the spec, as well as queueing information about who needs to review the spec, and why.';
+
+
+COMMENT ON COLUMN specification.assignee IS 'The person who has been assigned to implement this specification.';
+
+
+COMMENT ON COLUMN specification.drafter IS 'The person who has been asked to draft this specification. They are responsible for getting the spec to "approved" state.';
+
+
+COMMENT ON COLUMN specification.approver IS 'The person who is responsible for approving the specification in due course, and who will probably be required to review the code itself when it is being implemented.';
+
+
+COMMENT ON COLUMN specification.product IS 'The product for which this is a feature specification. The specification must be connected either to a product, or to a distribution.';
+
+
+COMMENT ON COLUMN specification.productseries IS 'This is an indicator that the specification is planned, or targeted, for implementation in a given product series. It is not necessary to target a spec to a series, but it is a useful way of showing which specs are planned to implement for a given series.';
+
+
+COMMENT ON COLUMN specification.distribution IS 'The distribution for which this is a feature specification. The specification must be connected either to a product, or to a distribution.';
+
+
+COMMENT ON COLUMN specification.distroseries IS 'If this is not NULL, then it means that the release managers have targeted this feature to be released in the given distroseries. It is not necessary to target a distroseries, but this is a useful way of know which specifications are, for example, BreezyGoals.';
+
+
+COMMENT ON COLUMN specification.milestone IS 'This is an indicator that the feature defined in this specification is expected to be delivered for a given milestone. Note that milestones are not necessarily releases, they are a way of identifying a point in time and grouping bugs and features around that.';
+
+
+COMMENT ON COLUMN specification.definition_status IS 'An enum called SpecificationDefinitionStatus that shows what the current status (new, draft, implemented etc) the spec is currently in.';
+
+
+COMMENT ON COLUMN specification.priority IS 'An enum that gives the implementation priority (low, medium, high, emergency) of the feature defined in this specification.';
+
+
+COMMENT ON COLUMN specification.specurl IS 'The URL where the specification itself can be found. This is usually a wiki page somewhere.';
+
+
+COMMENT ON COLUMN specification.whiteboard IS 'As long as the specification is somewhere else (i.e. not in Launchpad) it will be useful to have a place to hold some arbitrary message or status flags that have meaning to the project, not Launchpad. This whiteboard is just the place for it.';
+
+
+COMMENT ON COLUMN specification.superseded_by IS 'The specification which replaced this specification.';
+
+
+COMMENT ON COLUMN specification.implementation_status IS 'The implementation status of this specification. This field is used to track the actual delivery of the feature (implementing the spec), as opposed to the definition of expected behaviour (writing the spec).';
+
+
+COMMENT ON COLUMN specification.goalstatus IS 'Whether or not the drivers for the goal product series or distro release have accepted this specification as a goal.';
+
+
+COMMENT ON COLUMN specification.goal_proposer IS 'The person who proposed this spec as a goal for the productseries or distroseries.';
+
+
+COMMENT ON COLUMN specification.date_goal_proposed IS 'The date the spec was proposed as a goal.';
+
+
+COMMENT ON COLUMN specification.goal_decider IS 'The person who approved or declined this goal.';
+
+
+COMMENT ON COLUMN specification.date_goal_decided IS 'The date this goal was accepted or declined.';
+
+
+COMMENT ON COLUMN specification.completer IS 'The person who changed the state of the spec in such a way that it was determined to be completed.';
+
+
+COMMENT ON COLUMN specification.date_completed IS 'The date this specification was completed or marked obsolete. This lets us chart the progress of a project (or a release) over time in terms of features implemented.';
+
+
+COMMENT ON COLUMN specification.private IS 'Specification is private.';
+
+
+COMMENT ON CONSTRAINT specification_completion_fully_recorded_chk ON specification IS 'A constraint that ensures, where we have a date_completed, that we also have a completer. This means that the resolution was fully recorded.';
+
+
 CREATE SEQUENCE specification_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE specification_id_seq OWNED BY specification.id;
 
+
 CREATE TABLE specificationbranch (
     id integer NOT NULL,
     datecreated timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
@@ -4791,28 +14227,51 @@
     registrant integer NOT NULL
 );
 
+
+COMMENT ON TABLE specificationbranch IS 'A branch related to a specification, most likely a branch for implementing the specification.  It is possible to have multiple branches for a given specification especially in the situation where the specification requires modifying multiple products.';
+
+
+COMMENT ON COLUMN specificationbranch.specification IS 'The specification associated with this branch.';
+
+
+COMMENT ON COLUMN specificationbranch.branch IS 'The branch associated to the specification.';
+
+
+COMMENT ON COLUMN specificationbranch.registrant IS 'The person who linked the specification to the branch.';
+
+
 CREATE SEQUENCE specificationbranch_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE specificationbranch_id_seq OWNED BY specificationbranch.id;
 
+
 CREATE TABLE specificationbug (
     id integer NOT NULL,
     specification integer NOT NULL,
     bug integer NOT NULL
 );
 
+
+COMMENT ON TABLE specificationbug IS 'A table linking a specification and a bug. This is used to provide for easy navigation from bugs to related specs, and vice versa.';
+
+
 CREATE SEQUENCE specificationbug_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE specificationbug_id_seq OWNED BY specificationbug.id;
 
+
 CREATE TABLE specificationdependency (
     id integer NOT NULL,
     specification integer NOT NULL,
@@ -4821,14 +14280,27 @@
     CONSTRAINT specificationdependency_not_self CHECK ((specification <> dependency))
 );
 
+
+COMMENT ON TABLE specificationdependency IS 'A table that stores information about which specification needs to be implemented before another specification can be implemented. We can create a chain of dependencies, and use that information for scheduling and prioritisation of work.';
+
+
+COMMENT ON COLUMN specificationdependency.specification IS 'The spec for which we are creating a dependency.';
+
+
+COMMENT ON COLUMN specificationdependency.dependency IS 'The spec on which it is dependant.';
+
+
 CREATE SEQUENCE specificationdependency_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE specificationdependency_id_seq OWNED BY specificationdependency.id;
 
+
 CREATE TABLE specificationfeedback (
     id integer NOT NULL,
     specification integer NOT NULL,
@@ -4838,14 +14310,30 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE specificationfeedback IS 'A table representing a review request of a specification, from one user to another, with an optional message.';
+
+
+COMMENT ON COLUMN specificationfeedback.reviewer IS 'The person who has been asked to do the review.';
+
+
+COMMENT ON COLUMN specificationfeedback.requester IS 'The person who made the request.';
+
+
+COMMENT ON COLUMN specificationfeedback.queuemsg IS 'An optional text message for the reviewer, from the requester.';
+
+
 CREATE SEQUENCE specificationfeedback_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE specificationfeedback_id_seq OWNED BY specificationfeedback.id;
 
+
 CREATE TABLE specificationmessage (
     id integer NOT NULL,
     specification integer,
@@ -4853,14 +14341,21 @@
     visible boolean DEFAULT true NOT NULL
 );
 
+
+COMMENT ON TABLE specificationmessage IS 'Comments and discussion on a Specification.';
+
+
 CREATE SEQUENCE specificationmessage_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE specificationmessage_id_seq OWNED BY specificationmessage.id;
 
+
 CREATE TABLE specificationsubscription (
     id integer NOT NULL,
     specification integer NOT NULL,
@@ -4869,28 +14364,42 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE specificationsubscription IS 'A table capturing a subscription of a person to a specification.';
+
+
+COMMENT ON COLUMN specificationsubscription.essential IS 'A field that indicates whether or not this person is essential to discussions on the planned feature. This is used by the meeting scheduler to ensure that all the essential people are at any automatically scheduled BOFs discussing that spec.';
+
+
 CREATE SEQUENCE specificationsubscription_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE specificationsubscription_id_seq OWNED BY specificationsubscription.id;
 
+
 CREATE TABLE spokenin (
     language integer NOT NULL,
     country integer NOT NULL,
     id integer DEFAULT nextval(('spokenin_id_seq'::text)::regclass) NOT NULL
 );
 
+
 CREATE SEQUENCE spokenin_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE spokenin_id_seq OWNED BY spokenin.id;
 
+
 CREATE TABLE sprint (
     id integer NOT NULL,
     owner integer NOT NULL,
@@ -4911,14 +14420,39 @@
     CONSTRAINT sprint_starts_before_ends CHECK ((time_starts < time_ends))
 );
 
+
+COMMENT ON TABLE sprint IS 'A meeting, sprint or conference. This is a convenient way to keep track of a collection of specs that will be discussed, and the people that will be attending.';
+
+
+COMMENT ON COLUMN sprint.time_zone IS 'The timezone of the sprint, stored in text format from the Olsen database names, like "US/Eastern".';
+
+
+COMMENT ON COLUMN sprint.driver IS 'The driver (together with the registrant or owner) is responsible for deciding which topics will be accepted onto the agenda of the sprint.';
+
+
+COMMENT ON COLUMN sprint.homepage_content IS 'A home page for this sprint in the Launchpad.';
+
+
+COMMENT ON COLUMN sprint.icon IS 'The library file alias to a small image to be used as an icon whenever we are referring to a sprint.';
+
+
+COMMENT ON COLUMN sprint.mugshot IS 'The library file alias of a mugshot image to display as the branding of a sprint, on its home page.';
+
+
+COMMENT ON COLUMN sprint.logo IS 'The library file alias of a smaller version of this sprint''s mugshot.';
+
+
 CREATE SEQUENCE sprint_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sprint_id_seq OWNED BY sprint.id;
 
+
 CREATE TABLE sprintattendance (
     id integer NOT NULL,
     attendee integer NOT NULL,
@@ -4930,14 +14464,36 @@
     CONSTRAINT sprintattendance_starts_before_ends CHECK ((time_starts < time_ends))
 );
 
+
+COMMENT ON TABLE sprintattendance IS 'The record that someone will be attending a particular sprint or meeting.';
+
+
+COMMENT ON COLUMN sprintattendance.attendee IS 'The person attending the sprint.';
+
+
+COMMENT ON COLUMN sprintattendance.sprint IS 'The sprint the person is attending.';
+
+
+COMMENT ON COLUMN sprintattendance.time_starts IS 'The time from which the person will be available to participate in meetings at the sprint.';
+
+
+COMMENT ON COLUMN sprintattendance.time_ends IS 'The time of departure from the sprint or conference - this is the last time at which the person is available for meetings during the sprint.';
+
+
+COMMENT ON COLUMN sprintattendance.is_physical IS 'Is the person physically attending the sprint';
+
+
 CREATE SEQUENCE sprintattendance_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sprintattendance_id_seq OWNED BY sprintattendance.id;
 
+
 CREATE TABLE sprintspecification (
     id integer NOT NULL,
     sprint integer NOT NULL,
@@ -4951,14 +14507,36 @@
     CONSTRAINT sprintspecification_decision_recorded CHECK (((status = 30) OR ((decider IS NOT NULL) AND (date_decided IS NOT NULL))))
 );
 
+
+COMMENT ON TABLE sprintspecification IS 'The link between a sprint and a specification, so that we know which specs are going to be discussed at which sprint.';
+
+
+COMMENT ON COLUMN sprintspecification.status IS 'Whether or not the spec has been approved on the agenda for this sprint.';
+
+
+COMMENT ON COLUMN sprintspecification.whiteboard IS 'A place to store comments specifically related to this spec being on the agenda of this meeting.';
+
+
+COMMENT ON COLUMN sprintspecification.registrant IS 'The person who nominated this specification for the agenda of the sprint.';
+
+
+COMMENT ON COLUMN sprintspecification.decider IS 'The person who approved or declined this specification for the sprint agenda.';
+
+
+COMMENT ON COLUMN sprintspecification.date_decided IS 'The date this specification was approved or declined for the agenda.';
+
+
 CREATE SEQUENCE sprintspecification_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sprintspecification_id_seq OWNED BY sprintspecification.id;
 
+
 CREATE TABLE sshkey (
     id integer NOT NULL,
     person integer,
@@ -4968,49 +14546,17 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
 CREATE SEQUENCE sshkey_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE sshkey_id_seq OWNED BY sshkey.id;
 
-CREATE TABLE standardshipitrequest (
-    id integer NOT NULL,
-    quantityx86 integer NOT NULL,
-    quantityppc integer NOT NULL,
-    quantityamd64 integer NOT NULL,
-    isdefault boolean DEFAULT false NOT NULL,
-    flavour integer NOT NULL,
-    description text,
-    CONSTRAINT quantityamd64_is_positive CHECK ((quantityamd64 >= 0)),
-    CONSTRAINT quantityppc_is_positive CHECK ((quantityppc >= 0)),
-    CONSTRAINT quantityx86_is_positive CHECK ((quantityx86 >= 0))
-);
-
-CREATE SEQUENCE standardshipitrequest_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE standardshipitrequest_id_seq OWNED BY standardshipitrequest.id;
-
-CREATE TABLE staticdiff (
-    id integer NOT NULL,
-    from_revision_id text NOT NULL,
-    to_revision_id text NOT NULL,
-    diff integer NOT NULL
-);
-
-CREATE SEQUENCE staticdiff_id_seq
-    INCREMENT BY 1
-    NO MAXVALUE
-    NO MINVALUE
-    CACHE 1;
-
-ALTER SEQUENCE staticdiff_id_seq OWNED BY staticdiff.id;
 
 CREATE TABLE structuralsubscription (
     id integer NOT NULL,
@@ -5023,26 +14569,103 @@
     sourcepackagename integer,
     subscriber integer NOT NULL,
     subscribed_by integer NOT NULL,
-    bug_notification_level integer NOT NULL,
-    blueprint_notification_level integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     date_last_updated timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     CONSTRAINT one_target CHECK ((null_count(ARRAY[product, productseries, project, distroseries, distribution, milestone]) = 5)),
     CONSTRAINT sourcepackagename_requires_distribution CHECK (((sourcepackagename IS NULL) OR (distribution IS NOT NULL)))
 );
 
+
+COMMENT ON TABLE structuralsubscription IS 'A subscription to notifications about a Launchpad structure';
+
+
+COMMENT ON COLUMN structuralsubscription.product IS 'The subscription`s target, when it is a product.';
+
+
+COMMENT ON COLUMN structuralsubscription.productseries IS 'The subscription`s target, when it is a product series.';
+
+
+COMMENT ON COLUMN structuralsubscription.project IS 'The subscription`s target, when it is a project.';
+
+
+COMMENT ON COLUMN structuralsubscription.milestone IS 'The subscription`s target, when it is a milestone.';
+
+
+COMMENT ON COLUMN structuralsubscription.distribution IS 'The subscription`s target, when it is a distribution.';
+
+
+COMMENT ON COLUMN structuralsubscription.distroseries IS 'The subscription`s target, when it is a distribution series.';
+
+
+COMMENT ON COLUMN structuralsubscription.sourcepackagename IS 'The subscription`s target, when it is a source-package';
+
+
+COMMENT ON COLUMN structuralsubscription.subscriber IS 'The person subscribed.';
+
+
+COMMENT ON COLUMN structuralsubscription.subscribed_by IS 'The person initiating the subscription.';
+
+
+COMMENT ON COLUMN structuralsubscription.date_created IS 'The date on which this subscription was created.';
+
+
+COMMENT ON COLUMN structuralsubscription.date_last_updated IS 'The date on which this subscription was last updated.';
+
+
 CREATE SEQUENCE structuralsubscription_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE structuralsubscription_id_seq OWNED BY structuralsubscription.id;
 
+
+CREATE TABLE subunitstream (
+    id integer NOT NULL,
+    uploader integer NOT NULL,
+    date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
+    branch integer NOT NULL,
+    stream integer NOT NULL
+);
+
+
+COMMENT ON TABLE subunitstream IS 'Raw gz compressed subunit streams.';
+
+
+COMMENT ON COLUMN subunitstream.uploader IS 'The account used to upload the stream.';
+
+
+COMMENT ON COLUMN subunitstream.date_created IS 'The date of the upload.';
+
+
+COMMENT ON COLUMN subunitstream.branch IS 'The branch which the stream was created on/for/with.';
+
+
+COMMENT ON COLUMN subunitstream.stream IS 'The library file alias which contains the stream content.';
+
+
+CREATE SEQUENCE subunitstream_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+
+ALTER SEQUENCE subunitstream_id_seq OWNED BY subunitstream.id;
+
+
 CREATE TABLE suggestivepotemplate (
     potemplate integer NOT NULL
 );
 
+
+COMMENT ON TABLE suggestivepotemplate IS 'Cache of POTemplates that can provide external translation suggestions.';
+
+
 CREATE TABLE teammembership (
     id integer NOT NULL,
     person integer NOT NULL,
@@ -5065,28 +14688,103 @@
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL
 );
 
+
+COMMENT ON TABLE teammembership IS 'The direct membership of a person on a given team.';
+
+
+COMMENT ON COLUMN teammembership.person IS 'The person.';
+
+
+COMMENT ON COLUMN teammembership.team IS 'The team.';
+
+
+COMMENT ON COLUMN teammembership.status IS 'The state of the membership.';
+
+
+COMMENT ON COLUMN teammembership.date_joined IS 'The date this membership was made active for the first time.';
+
+
+COMMENT ON COLUMN teammembership.date_expires IS 'The date this membership will expire, if any.';
+
+
+COMMENT ON COLUMN teammembership.last_changed_by IS 'The person who reviewed the last change to this membership.';
+
+
+COMMENT ON COLUMN teammembership.last_change_comment IS 'The comment left by the reviewer for the change.';
+
+
+COMMENT ON COLUMN teammembership.proposed_by IS 'The user who proposed the person as member of the team.';
+
+
+COMMENT ON COLUMN teammembership.acknowledged_by IS 'The member (or someone acting on his behalf) who accepts an invitation to join a team';
+
+
+COMMENT ON COLUMN teammembership.reviewed_by IS 'The team admin who reviewed (approved/declined) the membership.';
+
+
+COMMENT ON COLUMN teammembership.date_proposed IS 'The date of the proposal.';
+
+
+COMMENT ON COLUMN teammembership.date_last_changed IS 'The date this membership was last changed.';
+
+
+COMMENT ON COLUMN teammembership.date_acknowledged IS 'The date of acknowledgement.';
+
+
+COMMENT ON COLUMN teammembership.date_reviewed IS 'The date the membership was
+approved/declined.';
+
+
+COMMENT ON COLUMN teammembership.proponent_comment IS 'The comment left by the proponent.';
+
+
+COMMENT ON COLUMN teammembership.acknowledger_comment IS 'The comment left by the person who acknowledged the membership.';
+
+
+COMMENT ON COLUMN teammembership.reviewer_comment IS 'The comment left by the approver.';
+
+
+COMMENT ON COLUMN teammembership.date_created IS 'The date this membership was created.';
+
+
 CREATE SEQUENCE teammembership_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE teammembership_id_seq OWNED BY teammembership.id;
 
+
 CREATE TABLE teamparticipation (
     id integer NOT NULL,
     team integer NOT NULL,
     person integer NOT NULL
 );
 
+
+COMMENT ON TABLE teamparticipation IS 'The participation of a person on a team, which can be a direct or indirect membership.';
+
+
+COMMENT ON COLUMN teamparticipation.team IS 'The team.';
+
+
+COMMENT ON COLUMN teamparticipation.person IS 'The member.';
+
+
 CREATE SEQUENCE teamparticipation_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE teamparticipation_id_seq OWNED BY teamparticipation.id;
 
+
 CREATE TABLE temporaryblobstorage (
     id integer NOT NULL,
     uuid text NOT NULL,
@@ -5094,14 +14792,18 @@
     file_alias integer NOT NULL
 );
 
+
 CREATE SEQUENCE temporaryblobstorage_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE temporaryblobstorage_id_seq OWNED BY temporaryblobstorage.id;
 
+
 CREATE TABLE translationgroup (
     id integer NOT NULL,
     name text NOT NULL,
@@ -5112,14 +14814,24 @@
     translation_guide_url text
 );
 
+
+COMMENT ON TABLE translationgroup IS 'This represents an organised translation group that spans multiple languages. Effectively it consists of a list of people (pointers to Person), and each Person is associated with a Language. So, for each TranslationGroup we can ask the question "in this TranslationGroup, who is responsible for translating into Arabic?", for example.';
+
+
+COMMENT ON COLUMN translationgroup.translation_guide_url IS 'URL with documentation about general rules for translation work done by this translation group.';
+
+
 CREATE SEQUENCE translationgroup_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE translationgroup_id_seq OWNED BY translationgroup.id;
 
+
 CREATE TABLE translationimportqueueentry (
     id integer NOT NULL,
     path text NOT NULL,
@@ -5129,7 +14841,7 @@
     distroseries integer,
     sourcepackagename integer,
     productseries integer,
-    is_published boolean NOT NULL,
+    by_maintainer boolean NOT NULL,
     pofile integer,
     potemplate integer,
     status integer DEFAULT 5 NOT NULL,
@@ -5139,17 +14851,65 @@
     CONSTRAINT valid_link CHECK ((((productseries IS NULL) <> (distroseries IS NULL)) AND ((distroseries IS NULL) = (sourcepackagename IS NULL))))
 );
 
+
+COMMENT ON TABLE translationimportqueueentry IS 'Queue with translatable resources pending to be imported into Rosetta.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.path IS 'The path (included the filename) where this file was stored when we imported it.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.content IS 'The file content that is being imported.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.importer IS 'The person that did the import.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.dateimported IS 'The timestamp when the import was done.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.distroseries IS 'The distribution release related to this import.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.sourcepackagename IS 'The source package name related to this import.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.productseries IS 'The product series related to this import.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.by_maintainer IS 'Notes whether is a published upload.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.pofile IS 'Link to the POFile where this import will end.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.potemplate IS 'Link to the POTemplate where this import will end.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.status IS 'The status of the import: 1 Approved, 2 Imported, 3 Deleted, 4 Failed, 5 Needs Review, 6 Blocked.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.date_status_changed IS 'The date when the status of this entry was changed.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.format IS 'The file format of the content that is being imported.';
+
+
+COMMENT ON COLUMN translationimportqueueentry.error_output IS 'Error output from last import attempt.';
+
+
 CREATE SEQUENCE translationimportqueueentry_id_seq
+    START WITH 1
     INCREMENT BY 1
     NO MAXVALUE
     NO MINVALUE
     CACHE 1;
 
+
 ALTER SEQUENCE translationimportqueueentry_id_seq OWNED BY translationimportqueueentry.id;
 
+
 CREATE TABLE translationmessage (
     id integer NOT NULL,
-    pofile integer,
     potmsgset integer NOT NULL,
     date_created timestamp without time zone DEFAULT timezone('UTC'::text, now()) NOT NULL,
     submitter integer NOT NULL,
@@ -5162,27 +14922,111 @@
     comment text,
     origin integer NOT NULL,
     validation_status integer DEFAULT 0 NOT NULL,
-    is_current boolean DEFAULT false NOT NULL,
+    is_current_ubuntu boolean DEFAULT false NOT NULL,
     is_fuzzy boolean DEFAULT false NOT NULL,
-    is_imported boolean DEFAULT false NOT NULL,
+    is_current_upstream boolean DEFAULT false NOT NULL,
     was_obsolete_in_last_import boolean DEFAULT false NOT NULL,
     was_fuzzy_in_last_import boolean DEFAULT false NOT NULL,
     msgstr4 integer,
     msgstr5 integer,
     potemplate integer,
     language integer,
-    variant text,
     CONSTRAINT translationmessage__reviewer__date_reviewed__valid CHECK (((reviewer IS NULL) = (date_reviewed IS NULL)))
 );
 
+
+COMMENT ON TABLE translationmessage IS 'This table stores a concrete
+translation for a POTMsgSet message. It knows who, when and where did it,
+and whether it was reviewed by someone and when was it reviewed.';
+
+
+COMMENT ON COLUMN translationmessage.potmsgset IS 'The template message which
+this translation message is a translation of.';
+
+
+COMMENT ON COLUMN translationmessage.date_created IS 'The date we saw this
+translation first.';
+
+
+COMMENT ON COLUMN translationmessage.submitter IS 'The person that made
+the submission through the web to Launchpad, or the last translator on the
+translation file that we are processing, or the person who uploaded that
+pofile to Launchpad. In short, our best guess as to the person who is
+contributing that translation.';
+
+
+COMMENT ON COLUMN translationmessage.date_reviewed IS 'The date when this
+message was reviewed for last time.';
+
+
+COMMENT ON COLUMN translationmessage.reviewer IS 'The person who did the
+review and accepted current translations.';
+
+
+COMMENT ON COLUMN translationmessage.msgstr0 IS 'Translation for plural form 0
+(if any).';
+
+
+COMMENT ON COLUMN translationmessage.msgstr1 IS 'Translation for plural form 1
+(if any).';
+
+
+COMMENT ON COLUMN translationmessage.msgstr2 IS 'Translation for plural form 2
+(if any).';
+
+
+COMMENT ON COLUMN translationmessage.msgstr3 IS 'Translation for plural form 3
+(if any).';
+
+
+COMMENT ON COLUMN translationmessage.comment IS 'Text of translator
+comment from the translation file.';
+
+
+COMMENT ON COLUMN translationmessage.origin IS 'The source of this
+translation. This indicates whether the translation was in a translation file
+that we parsed (probably one published in a package or branch or tarball), in
+which case its value will be 1, or was submitted through the web, in which
+case its value will be 2.';
+
+
+COMMENT ON COLUMN translationmessage.validation_status IS 'Whether we have
+validated this tra