Fix parsing of complex morphs to tsquery

When to_tsquery() or websearch_to_tsquery() meet a complex morph containing
multiple words residing adjacent position, these words are connected
with OP_AND operator.  That leads to surprising results.  For instace,
both websearch_to_tsquery('"pg_class pg"') and to_tsquery('pg_class <-> pg')
produce '( pg & class ) <-> pg' tsquery.  This tsquery requires
'pg' and 'class' words to reside on the same position and doesn't match
to to_tsvector('pg_class pg').  It appears to be ridiculous behavior, which
needs to be fixed.

This commit makes to_tsquery() or websearch_to_tsquery() connect words
residing adjacent position with OP_PHRASE.  Therefore, now those words are
normally chained with other OP_PHRASE operator.  The examples of above now
produces 'pg <-> class <-> pg' tsquery, which matches to
to_tsvector('pg_class pg').

Another effect of this commit is that complex morph word positions now need to
match the tsvector even if there is no surrounding OP_PHRASE.  This behavior
change generally looks like an improvement but making this commit not
backpatchable.

Reported-by: Barry Pederson
Bug: #16592
Discussion: https://postgr.es/m/16592-70b110ff9731c07d@postgresql.org
Discussion: https://postgr.es/m/CAPpHfdv0EzVhf6CWfB1_TTZqXV_2Sn-jSY3zSd7ePH%3D-%2B1V2DQ%40mail.gmail.com
Author: Alexander Korotkov
Reviewed-by: Tom Lane, Neil Chen
This commit is contained in:
Alexander Korotkov 2021-01-31 20:14:29 +03:00
parent dfb75e478c
commit 0c4f355c6a
3 changed files with 132 additions and 97 deletions

View File

@ -20,10 +20,20 @@
#include "utils/jsonfuncs.h"
/*
* Opaque data structure, which is passed by parse_tsquery() to pushval_morph().
*/
typedef struct MorphOpaque
{
Oid cfg_id;
int qoperator; /* query operator */
/*
* Single tsquery morph could be parsed into multiple words. When these
* words reside in adjacent positions, they are connected using this
* operator. Usually, that is OP_PHRASE, which requires word positions of
* a complex morph to exactly match the tsvector.
*/
int qoperator;
} MorphOpaque;
typedef struct TSVectorBuildState
@ -573,7 +583,14 @@ to_tsquery_byid(PG_FUNCTION_ARGS)
MorphOpaque data;
data.cfg_id = PG_GETARG_OID(0);
data.qoperator = OP_AND;
/*
* Passing OP_PHRASE as a qoperator makes tsquery require matching of word
* positions of a complex morph exactly match the tsvector. Also, when
* the complex morphs are connected with OP_PHRASE operator, we connect
* all their words into the OP_PHRASE sequence.
*/
data.qoperator = OP_PHRASE;
query = parse_tsquery(text_to_cstring(in),
pushval_morph,
@ -603,6 +620,12 @@ plainto_tsquery_byid(PG_FUNCTION_ARGS)
MorphOpaque data;
data.cfg_id = PG_GETARG_OID(0);
/*
* parse_tsquery() with P_TSQ_PLAIN flag takes the whole input text as a
* single morph. Passing OP_PHRASE as a qoperator makes tsquery require
* matching of all words independently on their positions.
*/
data.qoperator = OP_AND;
query = parse_tsquery(text_to_cstring(in),
@ -634,6 +657,12 @@ phraseto_tsquery_byid(PG_FUNCTION_ARGS)
MorphOpaque data;
data.cfg_id = PG_GETARG_OID(0);
/*
* parse_tsquery() with P_TSQ_PLAIN flag takes the whole input text as a
* single morph. Passing OP_PHRASE as a qoperator makes tsquery require
* matching of word positions.
*/
data.qoperator = OP_PHRASE;
query = parse_tsquery(text_to_cstring(in),
@ -665,7 +694,13 @@ websearch_to_tsquery_byid(PG_FUNCTION_ARGS)
data.cfg_id = PG_GETARG_OID(0);
data.qoperator = OP_AND;
/*
* Passing OP_PHRASE as a qoperator makes tsquery require matching of word
* positions of a complex morph exactly match the tsvector. Also, when
* the complex morphs are given in quotes, we connect all their words into
* the OP_PHRASE sequence.
*/
data.qoperator = OP_PHRASE;
query = parse_tsquery(text_to_cstring(in),
pushval_morph,

View File

@ -1997,31 +1997,31 @@ ALTER TABLE test_tsquery ADD COLUMN keyword tsquery;
UPDATE test_tsquery SET keyword = to_tsquery('english', txtkeyword);
ALTER TABLE test_tsquery ADD COLUMN sample tsquery;
UPDATE test_tsquery SET sample = to_tsquery('english', txtsample::text);
SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new <-> york';
count
-------
2
(1 row)
SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new <-> york';
count
-------
3
(1 row)
SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new <-> york';
count
-------
1
(1 row)
SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new <-> york';
count
-------
4
(1 row)
SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new <-> york';
count
-------
3
@ -2029,31 +2029,31 @@ SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new & york';
CREATE UNIQUE INDEX bt_tsq ON test_tsquery (keyword);
SET enable_seqscan=OFF;
SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new <-> york';
count
-------
2
(1 row)
SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new <-> york';
count
-------
3
(1 row)
SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new <-> york';
count
-------
1
(1 row)
SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new <-> york';
count
-------
4
(1 row)
SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new <-> york';
count
-------
3
@ -2085,10 +2085,10 @@ SELECT ts_rewrite('moscow & hotel', 'SELECT keyword, sample FROM test_tsquery'::
'hotel' & ( 'moskva' | 'moscow' )
(1 row)
SELECT ts_rewrite('bar & new & qq & foo & york', 'SELECT keyword, sample FROM test_tsquery'::text );
ts_rewrite
---------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' & 'appl' | 'new' & 'york' )
SELECT ts_rewrite('bar & qq & foo & (new <-> york)', 'SELECT keyword, sample FROM test_tsquery'::text );
ts_rewrite
-------------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' )
(1 row)
SELECT ts_rewrite( 'moscow', 'SELECT keyword, sample FROM test_tsquery');
@ -2103,10 +2103,10 @@ SELECT ts_rewrite( 'moscow & hotel', 'SELECT keyword, sample FROM test_tsquery')
'hotel' & ( 'moskva' | 'moscow' )
(1 row)
SELECT ts_rewrite( 'bar & new & qq & foo & york', 'SELECT keyword, sample FROM test_tsquery');
ts_rewrite
---------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' & 'appl' | 'new' & 'york' )
SELECT ts_rewrite( 'bar & qq & foo & (new <-> york)', 'SELECT keyword, sample FROM test_tsquery');
ts_rewrite
-------------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' )
(1 row)
SELECT ts_rewrite('1 & (2 <-> 3)', 'SELECT keyword, sample FROM test_tsquery'::text );
@ -2149,9 +2149,9 @@ NOTICE: text-search query doesn't contain lexemes: ""
(1 row)
SELECT keyword FROM test_tsquery WHERE keyword @> 'new';
keyword
----------------
'new' & 'york'
keyword
------------------
'new' <-> 'york'
(1 row)
SELECT keyword FROM test_tsquery WHERE keyword @> 'moscow';
@ -2183,10 +2183,10 @@ SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_t
'hotel' & ( 'moskva' | 'moscow' )
(1 row)
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & new & qq & foo & york') AS query;
ts_rewrite
---------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' & 'appl' | 'new' & 'york' )
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query;
ts_rewrite
-------------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' )
(1 row)
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query;
@ -2201,18 +2201,18 @@ SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_t
'hotel' & ( 'moskva' | 'moscow' )
(1 row)
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & new & qq & foo & york') AS query;
ts_rewrite
---------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' & 'appl' | 'new' & 'york' )
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query;
ts_rewrite
-------------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' )
(1 row)
CREATE INDEX qq ON test_tsquery USING gist (keyword tsquery_ops);
SET enable_seqscan=OFF;
SELECT keyword FROM test_tsquery WHERE keyword @> 'new';
keyword
----------------
'new' & 'york'
keyword
------------------
'new' <-> 'york'
(1 row)
SELECT keyword FROM test_tsquery WHERE keyword @> 'moscow';
@ -2244,10 +2244,10 @@ SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_t
'hotel' & ( 'moskva' | 'moscow' )
(1 row)
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & new & qq & foo & york') AS query;
ts_rewrite
---------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' & 'appl' | 'new' & 'york' )
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query;
ts_rewrite
-------------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' )
(1 row)
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query;
@ -2262,10 +2262,10 @@ SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_t
'hotel' & ( 'moskva' | 'moscow' )
(1 row)
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & new & qq & foo & york') AS query;
ts_rewrite
---------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' & 'appl' | 'new' & 'york' )
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query;
ts_rewrite
-------------------------------------------------------------------------------------
'citi' & 'foo' & ( 'bar' | 'qq' ) & ( 'nyc' | 'big' <-> 'appl' | 'new' <-> 'york' )
(1 row)
SELECT ts_rewrite(tsquery_phrase('foo', 'foo'), 'foo', 'bar | baz');
@ -2456,19 +2456,19 @@ select websearch_to_tsquery('simple', 'fat:A : cat:B');
select websearch_to_tsquery('simple', 'fat*rat');
websearch_to_tsquery
----------------------
'fat' & 'rat'
'fat' <-> 'rat'
(1 row)
select websearch_to_tsquery('simple', 'fat-rat');
websearch_to_tsquery
---------------------------
'fat-rat' & 'fat' & 'rat'
websearch_to_tsquery
-------------------------------
'fat-rat' <-> 'fat' <-> 'rat'
(1 row)
select websearch_to_tsquery('simple', 'fat_rat');
websearch_to_tsquery
----------------------
'fat' & 'rat'
'fat' <-> 'rat'
(1 row)
-- weights are completely ignored
@ -2665,64 +2665,64 @@ select websearch_to_tsquery('simple', 'abc OR1234');
(1 row)
select websearch_to_tsquery('simple', 'abc or-abc');
websearch_to_tsquery
---------------------------------
'abc' & 'or-abc' & 'or' & 'abc'
websearch_to_tsquery
-------------------------------------
'abc' & 'or-abc' <-> 'or' <-> 'abc'
(1 row)
select websearch_to_tsquery('simple', 'abc OR_abc');
websearch_to_tsquery
----------------------
'abc' & 'or' & 'abc'
websearch_to_tsquery
------------------------
'abc' & 'or' <-> 'abc'
(1 row)
-- test quotes
select websearch_to_tsquery('english', '"pg_class pg');
websearch_to_tsquery
-----------------------
'pg' & 'class' & 'pg'
websearch_to_tsquery
-------------------------
'pg' <-> 'class' & 'pg'
(1 row)
select websearch_to_tsquery('english', 'pg_class pg"');
websearch_to_tsquery
-----------------------
'pg' & 'class' & 'pg'
websearch_to_tsquery
-------------------------
'pg' <-> 'class' & 'pg'
(1 row)
select websearch_to_tsquery('english', '"pg_class pg"');
websearch_to_tsquery
-----------------------------
( 'pg' & 'class' ) <-> 'pg'
websearch_to_tsquery
---------------------------
'pg' <-> 'class' <-> 'pg'
(1 row)
select websearch_to_tsquery('english', 'abc "pg_class pg"');
websearch_to_tsquery
-------------------------------------
'abc' & ( 'pg' & 'class' ) <-> 'pg'
websearch_to_tsquery
-----------------------------------
'abc' & 'pg' <-> 'class' <-> 'pg'
(1 row)
select websearch_to_tsquery('english', '"pg_class pg" def');
websearch_to_tsquery
-------------------------------------
( 'pg' & 'class' ) <-> 'pg' & 'def'
websearch_to_tsquery
-----------------------------------
'pg' <-> 'class' <-> 'pg' & 'def'
(1 row)
select websearch_to_tsquery('english', 'abc "pg pg_class pg" def');
websearch_to_tsquery
------------------------------------------------------
'abc' & 'pg' <-> ( 'pg' & 'class' ) <-> 'pg' & 'def'
websearch_to_tsquery
--------------------------------------------------------
'abc' & 'pg' <-> ( 'pg' <-> 'class' ) <-> 'pg' & 'def'
(1 row)
select websearch_to_tsquery('english', ' or "pg pg_class pg" or ');
websearch_to_tsquery
--------------------------------------
'pg' <-> ( 'pg' & 'class' ) <-> 'pg'
websearch_to_tsquery
----------------------------------------
'pg' <-> ( 'pg' <-> 'class' ) <-> 'pg'
(1 row)
select websearch_to_tsquery('english', '""pg pg_class pg""');
websearch_to_tsquery
------------------------------
'pg' & 'pg' & 'class' & 'pg'
websearch_to_tsquery
--------------------------------
'pg' & 'pg' <-> 'class' & 'pg'
(1 row)
select websearch_to_tsquery('english', 'abc """"" def');
@ -2829,7 +2829,7 @@ NOTICE: text-search query contains only stop words or doesn't contain lexemes,
select websearch_to_tsquery('''abc''''def''');
websearch_to_tsquery
----------------------
'abc' & 'def'
'abc' <-> 'def'
(1 row)
select websearch_to_tsquery('\abc');

View File

@ -554,10 +554,10 @@ to_tsquery('english','Lorem') && phraseto_tsquery('english','ullamcorper urna'),
CREATE TABLE test_tsquery (txtkeyword TEXT, txtsample TEXT);
\set ECHO none
\copy test_tsquery from stdin
'New York' new & york | big & apple | nyc
'New York' new <-> york | big <-> apple | nyc
Moscow moskva | moscow
'Sanct Peter' Peterburg | peter | 'Sanct Peterburg'
'foo bar qq' foo & (bar | qq) & city
foo & bar & qq foo & (bar | qq) & city
1 & (2 <-> 3) 2 <-> 4
5 <-> 6 5 <-> 7
\.
@ -569,21 +569,21 @@ ALTER TABLE test_tsquery ADD COLUMN sample tsquery;
UPDATE test_tsquery SET sample = to_tsquery('english', txtsample::text);
SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new <-> york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new <-> york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new <-> york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new <-> york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new <-> york';
CREATE UNIQUE INDEX bt_tsq ON test_tsquery (keyword);
SET enable_seqscan=OFF;
SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new & york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword < 'new <-> york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword <= 'new <-> york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword = 'new <-> york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword >= 'new <-> york';
SELECT COUNT(*) FROM test_tsquery WHERE keyword > 'new <-> york';
RESET enable_seqscan;
@ -593,11 +593,11 @@ SELECT ts_rewrite(ts_rewrite('new & !york ', 'york', '!jersey'),
SELECT ts_rewrite('moscow', 'SELECT keyword, sample FROM test_tsquery'::text );
SELECT ts_rewrite('moscow & hotel', 'SELECT keyword, sample FROM test_tsquery'::text );
SELECT ts_rewrite('bar & new & qq & foo & york', 'SELECT keyword, sample FROM test_tsquery'::text );
SELECT ts_rewrite('bar & qq & foo & (new <-> york)', 'SELECT keyword, sample FROM test_tsquery'::text );
SELECT ts_rewrite( 'moscow', 'SELECT keyword, sample FROM test_tsquery');
SELECT ts_rewrite( 'moscow & hotel', 'SELECT keyword, sample FROM test_tsquery');
SELECT ts_rewrite( 'bar & new & qq & foo & york', 'SELECT keyword, sample FROM test_tsquery');
SELECT ts_rewrite( 'bar & qq & foo & (new <-> york)', 'SELECT keyword, sample FROM test_tsquery');
SELECT ts_rewrite('1 & (2 <-> 3)', 'SELECT keyword, sample FROM test_tsquery'::text );
SELECT ts_rewrite('1 & (2 <2> 3)', 'SELECT keyword, sample FROM test_tsquery'::text );
@ -614,10 +614,10 @@ SELECT keyword FROM test_tsquery WHERE keyword <@ 'new';
SELECT keyword FROM test_tsquery WHERE keyword <@ 'moscow';
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & new & qq & foo & york') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & new & qq & foo & york') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query;
CREATE INDEX qq ON test_tsquery USING gist (keyword tsquery_ops);
SET enable_seqscan=OFF;
@ -628,10 +628,10 @@ SELECT keyword FROM test_tsquery WHERE keyword <@ 'new';
SELECT keyword FROM test_tsquery WHERE keyword <@ 'moscow';
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & new & qq & foo & york') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'moscow & hotel') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & new & qq & foo & york') AS query;
SELECT ts_rewrite( query, 'SELECT keyword, sample FROM test_tsquery' ) FROM to_tsquery('english', 'bar & qq & foo & (new <-> york)') AS query;
SELECT ts_rewrite(tsquery_phrase('foo', 'foo'), 'foo', 'bar | baz');
SELECT to_tsvector('foo bar') @@