← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/postgresql-9.5-9.6 into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/postgresql-9.5-9.6 into lp:launchpad.

Commit message:
Update database schema for PostgreSQL 9.5 and 9.6.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/postgresql-9.5-9.6/+merge/310767

Update database schema for PostgreSQL 9.5 and 9.6. 9.3 still works, and 9.4 probably does too but it's not in any supported Ubuntu release so I don't much care.

There are three classes of PostgreSQL change that required us to adapt:

 - IS vs == operator precedence changed in 9.5.
 - tsquery parsing now treats '<' as the start of a phrase search operator as of 9.6.
 - A couple of system tables' columns changed in 9.4 and 9.6.

Check constraints are stored parsed, so changing history without a fresh patch is fine. But for functions we need to replace them entirely: http://paste.ubuntu.com/23475270/ is the effective diff to the original patches.

Patch 2209-21-4's creation of update_database_disk_utilization() failed
at application time, and wasn't depended on by any later patches, so
I've deleted that bit of history. For new databases the function won't
exist until 2209-81-0.

Text search query parsing has changed subtly: any '<' characters are
stripped ('<' begins 9.6's new phrase operator), meaning that terms such
as XML tags are no longer ignored. But such searches were already pretty
broken, and this arguably makes them slightly better.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/postgresql-9.5-9.6 into lp:launchpad.
=== modified file 'database/schema/patch-2209-11-1.sql'
--- database/schema/patch-2209-11-1.sql	2012-02-16 09:45:47 +0000
+++ database/schema/patch-2209-11-1.sql	2016-11-14 12:11:11 +0000
@@ -17,7 +17,7 @@
     product integer REFERENCES product,
     distribution integer REFERENCES distribution,
     type integer NOT NULL,
-    CONSTRAINT has_target CHECK (product IS NULL != distribution IS NULL)
+    CONSTRAINT has_target CHECK ((product IS NULL) != (distribution IS NULL))
 );
 
 CREATE UNIQUE INDEX accesspolicy__product__type__key
@@ -29,7 +29,7 @@
     id serial PRIMARY KEY,
     bug integer REFERENCES bug,
     branch integer, -- FK to be added later.
-    CONSTRAINT has_artifact CHECK (bug IS NULL != branch IS NULL)
+    CONSTRAINT has_artifact CHECK ((bug IS NULL) != (branch IS NULL))
 );
 
 CREATE UNIQUE INDEX accessartifact__bug__key

=== modified file 'database/schema/patch-2209-21-4.sql'
--- database/schema/patch-2209-21-4.sql	2012-06-01 12:02:00 +0000
+++ database/schema/patch-2209-21-4.sql	2016-11-14 12:11:11 +0000
@@ -1,110 +1,8 @@
-CREATE OR REPLACE 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 (
-        -- Tables
-        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
-        
-        -- 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_index,
-            pg_am
-        WHERE
-            pg_class_index.relkind = 'i'
-            AND pg_am.amname <> 'gin' -- pgstattuple doesn't support GIN
-            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_class_index.relam = pg_am.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;
-$$;
+SET client_min_messages = ERROR;
+
+-- This patch originally added update_database_disk_utilization(), but
+-- it failed to apply on PostgreSQL >=9.5. It has been replaced with a
+-- more compatible version in 2209-81-0. Deleting history is gross, but
+-- we can get away without properly altering it.
 
 INSERT INTO LaunchpadDatabaseRevision VALUES (2209, 21, 4);

=== modified file 'database/schema/patch-2209-30-1.sql'
--- database/schema/patch-2209-30-1.sql	2012-08-23 23:51:58 +0000
+++ database/schema/patch-2209-30-1.sql	2016-11-14 12:11:11 +0000
@@ -12,7 +12,7 @@
 -- If type is set, then either product or distribution must be set and person must be null.
 ALTER TABLE accesspolicy ADD CONSTRAINT has_target
     CHECK (
-      (type IS NOT NULL AND (product IS NULL <> distribution IS NULL) AND person IS NULL)
+      (type IS NOT NULL AND ((product IS NULL) <> (distribution IS NULL)) AND person IS NULL)
       OR
       (type IS NULL AND person IS NOT NULL and product IS NULL AND distribution IS NULL) );
 

=== added file 'database/schema/patch-2209-81-0.sql'
--- database/schema/patch-2209-81-0.sql	1970-01-01 00:00:00 +0000
+++ database/schema/patch-2209-81-0.sql	2016-11-14 12:11:11 +0000
@@ -0,0 +1,346 @@
+SET client_min_messages = ERROR;
+
+-- Update functions for PostgreSQL 9.5 and 9.6 support, in addition to 9.3.
+
+
+-- From 2209-00-0
+CREATE OR REPLACE 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;
+$$;
+
+
+-- From 2209-21-4
+CREATE OR REPLACE 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 (
+        -- Tables
+        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
+        
+        -- 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_index,
+            pg_am
+        WHERE
+            pg_class_index.relkind = 'i'
+            AND pg_am.amname <> 'gin' -- pgstattuple doesn't support GIN
+            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_class_index.relam = pg_am.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,
+            pg_index
+        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_index.indexrelid
+            AND pg_index.indrelid = pg_class_toast.oid
+        ) AS whatever;
+$$;
+
+
+-- From 2209-24-3
+CREATE OR REPLACE 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))
+
+        # Replace tsquery operators with ' '. '<' begins all the phrase
+        # search operators, and a standalone '>' is fine.
+        query = re.sub('[|&!<]', ' ', query)
+
+        # Normalize whitespace
+        query = re.sub("(?u)\s+"," ", query)
+
+        # Convert AND, OR, NOT to tsearch2 punctuation
+        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.
+        # ':' is used in queries to specify a weight of a word.
+        # '\' is treated differently in to_tsvector() and to_tsquery().
+        punctuation = r'[:\\]'
+        query = re.sub(r"(?u)%s+" % (punctuation,), " ", query)
+        ## plpy.debug('3 query is %s' % repr(query))
+
+        # Now that we have handle case sensitive booleans, convert to lowercase
+        query = query.lower()
+
+        # 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
+        $_$;
+
+
+-- From 2209-53-1
+CREATE OR REPLACE FUNCTION activity()
+RETURNS SETOF pg_stat_activity
+VOLATILE SECURITY DEFINER SET search_path = public
+LANGUAGE plpgsql AS $$
+DECLARE
+    a pg_stat_activity%ROWTYPE;
+BEGIN
+    IF EXISTS (
+            SELECT 1 FROM pg_attribute WHERE
+                attrelid =
+                    (SELECT oid FROM pg_class
+                     WHERE relname = 'pg_stat_activity')
+                AND attname = 'wait_event_type') THEN
+        -- >= 9.6
+        RETURN QUERY SELECT
+            datid, datname, pid, usesysid, usename, application_name,
+            client_addr, client_hostname, client_port, backend_start,
+            xact_start, query_start, state_change, wait_event_type,
+            wait_event, state, backend_xid, backend_xmin,
+            CASE
+                WHEN query LIKE '<IDLE>%'
+                    OR query LIKE 'autovacuum:%'
+                    THEN query
+                ELSE
+                    '<HIDDEN>'
+            END AS query
+        FROM pg_catalog.pg_stat_activity;
+    ELSIF EXISTS (
+            SELECT 1 FROM pg_attribute WHERE
+                attrelid =
+                    (SELECT oid FROM pg_class
+                     WHERE relname = 'pg_stat_activity')
+                AND attname = 'backend_xid') THEN
+        -- >= 9.4
+        RETURN QUERY SELECT
+            datid, datname, pid, usesysid, usename, application_name,
+            client_addr, client_hostname, client_port, backend_start,
+            xact_start, query_start, state_change, waiting, state,
+            backend_xid, backend_xmin,
+            CASE
+                WHEN query LIKE '<IDLE>%'
+                    OR query LIKE 'autovacuum:%'
+                    THEN query
+                ELSE
+                    '<HIDDEN>'
+            END AS query
+        FROM pg_catalog.pg_stat_activity;
+    ELSE
+        -- >= 9.2; anything older is unsupported
+        RETURN QUERY SELECT
+            datid, datname, pid, usesysid, usename, application_name,
+            client_addr, client_hostname, client_port, backend_start,
+            xact_start, query_start, state_change, waiting, state,
+            CASE
+                WHEN query LIKE '<IDLE>%'
+                    OR query LIKE 'autovacuum:%'
+                    THEN query
+                ELSE
+                    '<HIDDEN>'
+            END AS query
+        FROM pg_catalog.pg_stat_activity;
+    END IF;
+END;
+$$;
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2209, 81, 0);

=== modified file 'lib/lp/services/database/doc/textsearching.txt'
--- lib/lp/services/database/doc/textsearching.txt	2016-01-26 15:47:37 +0000
+++ lib/lp/services/database/doc/textsearching.txt	2016-11-14 12:11:11 +0000
@@ -340,14 +340,12 @@
 
 XXX Abel Deuring 2012-06-20 bug=1015519: XML tags cannot be searched.
 
-    >>> print search_same('foo <bar> baz')
-    FTI data: 'baz':2 'foo':1 query: 'foo' & 'baz' match: True
-
-More specifically, tags are simply dropped from the FTI data and from
-search queries.
+Tags are simply dropped from the FTI data. The terms show up without
+brackets in parsed queries as a consequence of phrase operator stripping
+added for PostgreSQL 9.6.
 
     >>> print search('some text <div>whatever</div>', '<div>')
-    FTI data: 'text':2 'whatev':3 query: None match: None
+    FTI data: 'text':2 'whatev':3 query: 'div' match: False
 
 Of course, omitting '<' and '>'from the query does not help.
 

=== modified file 'utilities/launchpad-database-setup'
--- utilities/launchpad-database-setup	2014-12-20 16:38:50 +0000
+++ utilities/launchpad-database-setup	2016-11-14 12:11:11 +0000
@@ -18,7 +18,7 @@
 # https://dev.launchpad.net/DatabaseSetup which are intended for
 # initial Launchpad setup on an otherwise unconfigured postgresql instance
 
-for pgversion in 9.1 9.3
+for pgversion in 9.3 9.5 9.6
 do
   sudo grep -qs "^auto" /etc/postgresql/$pgversion/main/start.conf
   if [ $? -eq 0 ]; then


Follow ups