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

Proposed by William Grant
Status: Merged
Merged at revision: 18275
Proposed branch: lp:~wgrant/launchpad/postgresql-9.5-9.6
Merge into: lp:launchpad
Diff against target: 542 lines (+360/-118)
6 files modified
database/schema/patch-2209-11-1.sql (+2/-2)
database/schema/patch-2209-21-4.sql (+6/-108)
database/schema/patch-2209-30-1.sql (+1/-1)
database/schema/patch-2209-81-0.sql (+346/-0)
lib/lp/services/database/doc/textsearching.txt (+4/-6)
utilities/launchpad-database-setup (+1/-1)
To merge this branch: bzr merge lp:~wgrant/launchpad/postgresql-9.5-9.6
Reviewer Review Type Date Requested Status
Colin Watson (community) db Approve
Review via email: mp+310767@code.launchpad.net

Commit message

Update database schema for PostgreSQL 9.5 and 9.6.

Description of the change

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.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve (db)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/patch-2209-11-1.sql'
2--- database/schema/patch-2209-11-1.sql 2012-02-16 09:45:47 +0000
3+++ database/schema/patch-2209-11-1.sql 2016-11-14 12:11:11 +0000
4@@ -17,7 +17,7 @@
5 product integer REFERENCES product,
6 distribution integer REFERENCES distribution,
7 type integer NOT NULL,
8- CONSTRAINT has_target CHECK (product IS NULL != distribution IS NULL)
9+ CONSTRAINT has_target CHECK ((product IS NULL) != (distribution IS NULL))
10 );
11
12 CREATE UNIQUE INDEX accesspolicy__product__type__key
13@@ -29,7 +29,7 @@
14 id serial PRIMARY KEY,
15 bug integer REFERENCES bug,
16 branch integer, -- FK to be added later.
17- CONSTRAINT has_artifact CHECK (bug IS NULL != branch IS NULL)
18+ CONSTRAINT has_artifact CHECK ((bug IS NULL) != (branch IS NULL))
19 );
20
21 CREATE UNIQUE INDEX accessartifact__bug__key
22
23=== modified file 'database/schema/patch-2209-21-4.sql'
24--- database/schema/patch-2209-21-4.sql 2012-06-01 12:02:00 +0000
25+++ database/schema/patch-2209-21-4.sql 2016-11-14 12:11:11 +0000
26@@ -1,110 +1,8 @@
27-CREATE OR REPLACE FUNCTION update_database_disk_utilization() RETURNS void
28- LANGUAGE sql SECURITY DEFINER
29- SET search_path TO public
30- AS $$
31- INSERT INTO DatabaseDiskUtilization
32- SELECT
33- CURRENT_TIMESTAMP AT TIME ZONE 'UTC',
34- namespace, name,
35- sub_namespace, sub_name,
36- kind,
37- (namespace || '.' || name || COALESCE(
38- '/' || sub_namespace || '.' || sub_name, '')) AS sort,
39- (stat).table_len,
40- (stat).tuple_count,
41- (stat).tuple_len,
42- (stat).tuple_percent,
43- (stat).dead_tuple_count,
44- (stat).dead_tuple_len,
45- (stat).dead_tuple_percent,
46- (stat).free_space,
47- (stat).free_percent
48- FROM (
49- -- Tables
50- SELECT
51- pg_namespace.nspname AS namespace,
52- pg_class.relname AS name,
53- NULL AS sub_namespace,
54- NULL AS sub_name,
55- pg_class.relkind AS kind,
56- pgstattuple(pg_class.oid) AS stat
57- FROM pg_class, pg_namespace
58- WHERE
59- pg_class.relnamespace = pg_namespace.oid
60- AND pg_class.relkind = 'r'
61- AND pg_table_is_visible(pg_class.oid)
62-
63- UNION ALL
64-
65- -- Indexes
66- SELECT
67- pg_namespace_table.nspname AS namespace,
68- pg_class_table.relname AS name,
69- pg_namespace_index.nspname AS sub_namespace,
70- pg_class_index.relname AS sub_name,
71- pg_class_index.relkind AS kind,
72- pgstattuple(pg_class_index.oid) AS stat
73- FROM
74- pg_namespace AS pg_namespace_table,
75- pg_namespace AS pg_namespace_index,
76- pg_class AS pg_class_table,
77- pg_class AS pg_class_index,
78- pg_index,
79- pg_am
80- WHERE
81- pg_class_index.relkind = 'i'
82- AND pg_am.amname <> 'gin' -- pgstattuple doesn't support GIN
83- AND pg_table_is_visible(pg_class_table.oid)
84- AND pg_class_index.relnamespace = pg_namespace_index.oid
85- AND pg_class_table.relnamespace = pg_namespace_table.oid
86- AND pg_class_index.relam = pg_am.oid
87- AND pg_index.indexrelid = pg_class_index.oid
88- AND pg_index.indrelid = pg_class_table.oid
89-
90- UNION ALL
91-
92- -- TOAST tables
93- SELECT
94- pg_namespace_table.nspname AS namespace,
95- pg_class_table.relname AS name,
96- pg_namespace_toast.nspname AS sub_namespace,
97- pg_class_toast.relname AS sub_name,
98- pg_class_toast.relkind AS kind,
99- pgstattuple(pg_class_toast.oid) AS stat
100- FROM
101- pg_namespace AS pg_namespace_table,
102- pg_namespace AS pg_namespace_toast,
103- pg_class AS pg_class_table,
104- pg_class AS pg_class_toast
105- WHERE
106- pg_class_toast.relnamespace = pg_namespace_toast.oid
107- AND pg_table_is_visible(pg_class_table.oid)
108- AND pg_class_table.relnamespace = pg_namespace_table.oid
109- AND pg_class_toast.oid = pg_class_table.reltoastrelid
110-
111- UNION ALL
112-
113- -- TOAST indexes
114- SELECT
115- pg_namespace_table.nspname AS namespace,
116- pg_class_table.relname AS name,
117- pg_namespace_index.nspname AS sub_namespace,
118- pg_class_index.relname AS sub_name,
119- pg_class_index.relkind AS kind,
120- pgstattuple(pg_class_index.oid) AS stat
121- FROM
122- pg_namespace AS pg_namespace_table,
123- pg_namespace AS pg_namespace_index,
124- pg_class AS pg_class_table,
125- pg_class AS pg_class_index,
126- pg_class AS pg_class_toast
127- WHERE
128- pg_class_table.relnamespace = pg_namespace_table.oid
129- AND pg_table_is_visible(pg_class_table.oid)
130- AND pg_class_index.relnamespace = pg_namespace_index.oid
131- AND pg_class_table.reltoastrelid = pg_class_toast.oid
132- AND pg_class_index.oid = pg_class_toast.reltoastidxid
133- ) AS whatever;
134-$$;
135+SET client_min_messages = ERROR;
136+
137+-- This patch originally added update_database_disk_utilization(), but
138+-- it failed to apply on PostgreSQL >=9.5. It has been replaced with a
139+-- more compatible version in 2209-81-0. Deleting history is gross, but
140+-- we can get away without properly altering it.
141
142 INSERT INTO LaunchpadDatabaseRevision VALUES (2209, 21, 4);
143
144=== modified file 'database/schema/patch-2209-30-1.sql'
145--- database/schema/patch-2209-30-1.sql 2012-08-23 23:51:58 +0000
146+++ database/schema/patch-2209-30-1.sql 2016-11-14 12:11:11 +0000
147@@ -12,7 +12,7 @@
148 -- If type is set, then either product or distribution must be set and person must be null.
149 ALTER TABLE accesspolicy ADD CONSTRAINT has_target
150 CHECK (
151- (type IS NOT NULL AND (product IS NULL <> distribution IS NULL) AND person IS NULL)
152+ (type IS NOT NULL AND ((product IS NULL) <> (distribution IS NULL)) AND person IS NULL)
153 OR
154 (type IS NULL AND person IS NOT NULL and product IS NULL AND distribution IS NULL) );
155
156
157=== added file 'database/schema/patch-2209-81-0.sql'
158--- database/schema/patch-2209-81-0.sql 1970-01-01 00:00:00 +0000
159+++ database/schema/patch-2209-81-0.sql 2016-11-14 12:11:11 +0000
160@@ -0,0 +1,346 @@
161+SET client_min_messages = ERROR;
162+
163+-- Update functions for PostgreSQL 9.5 and 9.6 support, in addition to 9.3.
164+
165+
166+-- From 2209-00-0
167+CREATE OR REPLACE FUNCTION update_branch_name_cache() RETURNS trigger
168+ LANGUAGE plpgsql
169+ AS $$
170+DECLARE
171+ needs_update boolean := FALSE;
172+BEGIN
173+ IF TG_OP = 'INSERT' THEN
174+ needs_update := TRUE;
175+ ELSIF (NEW.owner_name IS NULL
176+ OR NEW.unique_name IS NULL
177+ OR OLD.owner_name <> NEW.owner_name
178+ OR OLD.unique_name <> NEW.unique_name
179+ OR ((NEW.target_suffix IS NULL) <> (OLD.target_suffix IS NULL))
180+ OR COALESCE(OLD.target_suffix, '') <> COALESCE(NEW.target_suffix, '')
181+ OR OLD.name <> NEW.name
182+ OR OLD.owner <> NEW.owner
183+ OR COALESCE(OLD.product, -1) <> COALESCE(NEW.product, -1)
184+ OR COALESCE(OLD.distroseries, -1) <> COALESCE(NEW.distroseries, -1)
185+ OR COALESCE(OLD.sourcepackagename, -1)
186+ <> COALESCE(NEW.sourcepackagename, -1)) THEN
187+ needs_update := TRUE;
188+ END IF;
189+
190+ IF needs_update THEN
191+ SELECT
192+ Person.name AS owner_name,
193+ COALESCE(Product.name, SPN.name) AS target_suffix,
194+ '~' || Person.name || '/' || COALESCE(
195+ Product.name,
196+ Distribution.name || '/' || Distroseries.name
197+ || '/' || SPN.name,
198+ '+junk') || '/' || NEW.name AS unique_name
199+ INTO NEW.owner_name, NEW.target_suffix, NEW.unique_name
200+ FROM Person
201+ LEFT OUTER JOIN DistroSeries ON NEW.distroseries = DistroSeries.id
202+ LEFT OUTER JOIN Product ON NEW.product = Product.id
203+ LEFT OUTER JOIN Distribution
204+ ON Distroseries.distribution = Distribution.id
205+ LEFT OUTER JOIN SourcepackageName AS SPN
206+ ON SPN.id = NEW.sourcepackagename
207+ WHERE Person.id = NEW.owner;
208+ END IF;
209+
210+ RETURN NEW;
211+END;
212+$$;
213+
214+
215+-- From 2209-21-4
216+CREATE OR REPLACE FUNCTION update_database_disk_utilization() RETURNS void
217+ LANGUAGE sql SECURITY DEFINER
218+ SET search_path TO public
219+ AS $$
220+ INSERT INTO DatabaseDiskUtilization
221+ SELECT
222+ CURRENT_TIMESTAMP AT TIME ZONE 'UTC',
223+ namespace, name,
224+ sub_namespace, sub_name,
225+ kind,
226+ (namespace || '.' || name || COALESCE(
227+ '/' || sub_namespace || '.' || sub_name, '')) AS sort,
228+ (stat).table_len,
229+ (stat).tuple_count,
230+ (stat).tuple_len,
231+ (stat).tuple_percent,
232+ (stat).dead_tuple_count,
233+ (stat).dead_tuple_len,
234+ (stat).dead_tuple_percent,
235+ (stat).free_space,
236+ (stat).free_percent
237+ FROM (
238+ -- Tables
239+ SELECT
240+ pg_namespace.nspname AS namespace,
241+ pg_class.relname AS name,
242+ NULL AS sub_namespace,
243+ NULL AS sub_name,
244+ pg_class.relkind AS kind,
245+ pgstattuple(pg_class.oid) AS stat
246+ FROM pg_class, pg_namespace
247+ WHERE
248+ pg_class.relnamespace = pg_namespace.oid
249+ AND pg_class.relkind = 'r'
250+ AND pg_table_is_visible(pg_class.oid)
251+
252+ UNION ALL
253+
254+ -- Indexes
255+ SELECT
256+ pg_namespace_table.nspname AS namespace,
257+ pg_class_table.relname AS name,
258+ pg_namespace_index.nspname AS sub_namespace,
259+ pg_class_index.relname AS sub_name,
260+ pg_class_index.relkind AS kind,
261+ pgstattuple(pg_class_index.oid) AS stat
262+ FROM
263+ pg_namespace AS pg_namespace_table,
264+ pg_namespace AS pg_namespace_index,
265+ pg_class AS pg_class_table,
266+ pg_class AS pg_class_index,
267+ pg_index,
268+ pg_am
269+ WHERE
270+ pg_class_index.relkind = 'i'
271+ AND pg_am.amname <> 'gin' -- pgstattuple doesn't support GIN
272+ AND pg_table_is_visible(pg_class_table.oid)
273+ AND pg_class_index.relnamespace = pg_namespace_index.oid
274+ AND pg_class_table.relnamespace = pg_namespace_table.oid
275+ AND pg_class_index.relam = pg_am.oid
276+ AND pg_index.indexrelid = pg_class_index.oid
277+ AND pg_index.indrelid = pg_class_table.oid
278+
279+ UNION ALL
280+
281+ -- TOAST tables
282+ SELECT
283+ pg_namespace_table.nspname AS namespace,
284+ pg_class_table.relname AS name,
285+ pg_namespace_toast.nspname AS sub_namespace,
286+ pg_class_toast.relname AS sub_name,
287+ pg_class_toast.relkind AS kind,
288+ pgstattuple(pg_class_toast.oid) AS stat
289+ FROM
290+ pg_namespace AS pg_namespace_table,
291+ pg_namespace AS pg_namespace_toast,
292+ pg_class AS pg_class_table,
293+ pg_class AS pg_class_toast
294+ WHERE
295+ pg_class_toast.relnamespace = pg_namespace_toast.oid
296+ AND pg_table_is_visible(pg_class_table.oid)
297+ AND pg_class_table.relnamespace = pg_namespace_table.oid
298+ AND pg_class_toast.oid = pg_class_table.reltoastrelid
299+
300+ UNION ALL
301+
302+ -- TOAST indexes
303+ SELECT
304+ pg_namespace_table.nspname AS namespace,
305+ pg_class_table.relname AS name,
306+ pg_namespace_index.nspname AS sub_namespace,
307+ pg_class_index.relname AS sub_name,
308+ pg_class_index.relkind AS kind,
309+ pgstattuple(pg_class_index.oid) AS stat
310+ FROM
311+ pg_namespace AS pg_namespace_table,
312+ pg_namespace AS pg_namespace_index,
313+ pg_class AS pg_class_table,
314+ pg_class AS pg_class_index,
315+ pg_class AS pg_class_toast,
316+ pg_index
317+ WHERE
318+ pg_class_table.relnamespace = pg_namespace_table.oid
319+ AND pg_table_is_visible(pg_class_table.oid)
320+ AND pg_class_index.relnamespace = pg_namespace_index.oid
321+ AND pg_class_table.reltoastrelid = pg_class_toast.oid
322+ AND pg_class_index.oid = pg_index.indexrelid
323+ AND pg_index.indrelid = pg_class_toast.oid
324+ ) AS whatever;
325+$$;
326+
327+
328+-- From 2209-24-3
329+CREATE OR REPLACE FUNCTION _ftq(text) RETURNS text
330+ LANGUAGE plpythonu IMMUTABLE STRICT
331+ AS $_$
332+ import re
333+
334+ # I think this method would be more robust if we used a real
335+ # tokenizer and parser to generate the query string, but we need
336+ # something suitable for use as a stored procedure which currently
337+ # means no external dependancies.
338+
339+ # Convert to Unicode
340+ query = args[0].decode('utf8')
341+ ## plpy.debug('1 query is %s' % repr(query))
342+
343+ # Replace tsquery operators with ' '. '<' begins all the phrase
344+ # search operators, and a standalone '>' is fine.
345+ query = re.sub('[|&!<]', ' ', query)
346+
347+ # Normalize whitespace
348+ query = re.sub("(?u)\s+"," ", query)
349+
350+ # Convert AND, OR, NOT to tsearch2 punctuation
351+ query = re.sub(r"(?u)\bAND\b", "&", query)
352+ query = re.sub(r"(?u)\bOR\b", "|", query)
353+ query = re.sub(r"(?u)\bNOT\b", " !", query)
354+ ## plpy.debug('2 query is %s' % repr(query))
355+
356+ # Deal with unwanted punctuation.
357+ # ':' is used in queries to specify a weight of a word.
358+ # '\' is treated differently in to_tsvector() and to_tsquery().
359+ punctuation = r'[:\\]'
360+ query = re.sub(r"(?u)%s+" % (punctuation,), " ", query)
361+ ## plpy.debug('3 query is %s' % repr(query))
362+
363+ # Now that we have handle case sensitive booleans, convert to lowercase
364+ query = query.lower()
365+
366+ # Remove unpartnered bracket on the left and right
367+ query = re.sub(r"(?ux) ^ ( [^(]* ) \)", r"(\1)", query)
368+ query = re.sub(r"(?ux) \( ( [^)]* ) $", r"(\1)", query)
369+
370+ # Remove spurious brackets
371+ query = re.sub(r"(?u)\(([^\&\|]*?)\)", r" \1 ", query)
372+ ## plpy.debug('5 query is %s' % repr(query))
373+
374+ # Insert & between tokens without an existing boolean operator
375+ # ( not proceeded by (|&!
376+ query = re.sub(r"(?u)(?<![\(\|\&\!])\s*\(", "&(", query)
377+ ## plpy.debug('6 query is %s' % repr(query))
378+ # ) not followed by )|&
379+ query = re.sub(r"(?u)\)(?!\s*(\)|\||\&|\s*$))", ")&", query)
380+ ## plpy.debug('6.1 query is %s' % repr(query))
381+ # Whitespace not proceded by (|&! not followed by &|
382+ query = re.sub(r"(?u)(?<![\(\|\&\!\s])\s+(?![\&\|\s])", "&", query)
383+ ## plpy.debug('7 query is %s' % repr(query))
384+
385+ # Detect and repair syntax errors - we are lenient because
386+ # this input is generally from users.
387+
388+ # Fix unbalanced brackets
389+ openings = query.count("(")
390+ closings = query.count(")")
391+ if openings > closings:
392+ query = query + " ) "*(openings-closings)
393+ elif closings > openings:
394+ query = " ( "*(closings-openings) + query
395+ ## plpy.debug('8 query is %s' % repr(query))
396+
397+ # Strip ' character that do not have letters on both sides
398+ query = re.sub(r"(?u)((?<!\w)'|'(?!\w))", "", query)
399+
400+ # Brackets containing nothing but whitespace and booleans, recursive
401+ last = ""
402+ while last != query:
403+ last = query
404+ query = re.sub(r"(?u)\([\s\&\|\!]*\)", "", query)
405+ ## plpy.debug('9 query is %s' % repr(query))
406+
407+ # An & or | following a (
408+ query = re.sub(r"(?u)(?<=\()[\&\|\s]+", "", query)
409+ ## plpy.debug('10 query is %s' % repr(query))
410+
411+ # An &, | or ! immediatly before a )
412+ query = re.sub(r"(?u)[\&\|\!\s]*[\&\|\!]+\s*(?=\))", "", query)
413+ ## plpy.debug('11 query is %s' % repr(query))
414+
415+ # An &,| or ! followed by another boolean.
416+ query = re.sub(r"(?ux) \s* ( [\&\|\!] ) [\s\&\|]+", r"\1", query)
417+ ## plpy.debug('12 query is %s' % repr(query))
418+
419+ # Leading & or |
420+ query = re.sub(r"(?u)^[\s\&\|]+", "", query)
421+ ## plpy.debug('13 query is %s' % repr(query))
422+
423+ # Trailing &, | or !
424+ query = re.sub(r"(?u)[\&\|\!\s]+$", "", query)
425+ ## plpy.debug('14 query is %s' % repr(query))
426+
427+ # If we have nothing but whitespace and tsearch2 operators,
428+ # return NULL.
429+ if re.search(r"(?u)^[\&\|\!\s\(\)]*$", query) is not None:
430+ return None
431+
432+ # Convert back to UTF-8
433+ query = query.encode('utf8')
434+ ## plpy.debug('15 query is %s' % repr(query))
435+
436+ return query or None
437+ $_$;
438+
439+
440+-- From 2209-53-1
441+CREATE OR REPLACE FUNCTION activity()
442+RETURNS SETOF pg_stat_activity
443+VOLATILE SECURITY DEFINER SET search_path = public
444+LANGUAGE plpgsql AS $$
445+DECLARE
446+ a pg_stat_activity%ROWTYPE;
447+BEGIN
448+ IF EXISTS (
449+ SELECT 1 FROM pg_attribute WHERE
450+ attrelid =
451+ (SELECT oid FROM pg_class
452+ WHERE relname = 'pg_stat_activity')
453+ AND attname = 'wait_event_type') THEN
454+ -- >= 9.6
455+ RETURN QUERY SELECT
456+ datid, datname, pid, usesysid, usename, application_name,
457+ client_addr, client_hostname, client_port, backend_start,
458+ xact_start, query_start, state_change, wait_event_type,
459+ wait_event, state, backend_xid, backend_xmin,
460+ CASE
461+ WHEN query LIKE '<IDLE>%'
462+ OR query LIKE 'autovacuum:%'
463+ THEN query
464+ ELSE
465+ '<HIDDEN>'
466+ END AS query
467+ FROM pg_catalog.pg_stat_activity;
468+ ELSIF EXISTS (
469+ SELECT 1 FROM pg_attribute WHERE
470+ attrelid =
471+ (SELECT oid FROM pg_class
472+ WHERE relname = 'pg_stat_activity')
473+ AND attname = 'backend_xid') THEN
474+ -- >= 9.4
475+ RETURN QUERY SELECT
476+ datid, datname, pid, usesysid, usename, application_name,
477+ client_addr, client_hostname, client_port, backend_start,
478+ xact_start, query_start, state_change, waiting, state,
479+ backend_xid, backend_xmin,
480+ CASE
481+ WHEN query LIKE '<IDLE>%'
482+ OR query LIKE 'autovacuum:%'
483+ THEN query
484+ ELSE
485+ '<HIDDEN>'
486+ END AS query
487+ FROM pg_catalog.pg_stat_activity;
488+ ELSE
489+ -- >= 9.2; anything older is unsupported
490+ RETURN QUERY SELECT
491+ datid, datname, pid, usesysid, usename, application_name,
492+ client_addr, client_hostname, client_port, backend_start,
493+ xact_start, query_start, state_change, waiting, state,
494+ CASE
495+ WHEN query LIKE '<IDLE>%'
496+ OR query LIKE 'autovacuum:%'
497+ THEN query
498+ ELSE
499+ '<HIDDEN>'
500+ END AS query
501+ FROM pg_catalog.pg_stat_activity;
502+ END IF;
503+END;
504+$$;
505+
506+INSERT INTO LaunchpadDatabaseRevision VALUES (2209, 81, 0);
507
508=== modified file 'lib/lp/services/database/doc/textsearching.txt'
509--- lib/lp/services/database/doc/textsearching.txt 2016-01-26 15:47:37 +0000
510+++ lib/lp/services/database/doc/textsearching.txt 2016-11-14 12:11:11 +0000
511@@ -340,14 +340,12 @@
512
513 XXX Abel Deuring 2012-06-20 bug=1015519: XML tags cannot be searched.
514
515- >>> print search_same('foo <bar> baz')
516- FTI data: 'baz':2 'foo':1 query: 'foo' & 'baz' match: True
517-
518-More specifically, tags are simply dropped from the FTI data and from
519-search queries.
520+Tags are simply dropped from the FTI data. The terms show up without
521+brackets in parsed queries as a consequence of phrase operator stripping
522+added for PostgreSQL 9.6.
523
524 >>> print search('some text <div>whatever</div>', '<div>')
525- FTI data: 'text':2 'whatev':3 query: None match: None
526+ FTI data: 'text':2 'whatev':3 query: 'div' match: False
527
528 Of course, omitting '<' and '>'from the query does not help.
529
530
531=== modified file 'utilities/launchpad-database-setup'
532--- utilities/launchpad-database-setup 2014-12-20 16:38:50 +0000
533+++ utilities/launchpad-database-setup 2016-11-14 12:11:11 +0000
534@@ -18,7 +18,7 @@
535 # https://dev.launchpad.net/DatabaseSetup which are intended for
536 # initial Launchpad setup on an otherwise unconfigured postgresql instance
537
538-for pgversion in 9.1 9.3
539+for pgversion in 9.3 9.5 9.6
540 do
541 sudo grep -qs "^auto" /etc/postgresql/$pgversion/main/start.conf
542 if [ $? -eq 0 ]; then