<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://rt-wiki.bestpractical.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ktm</id>
	<title>Request Tracker Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://rt-wiki.bestpractical.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ktm"/>
	<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/wiki/Special:Contributions/Ktm"/>
	<updated>2026-08-22T17:41:06Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.1</generator>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=PostgreSQLFullText&amp;diff=2628</id>
		<title>PostgreSQLFullText</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=PostgreSQLFullText&amp;diff=2628"/>
		<updated>2009-03-02T19:30:24Z</updated>

		<summary type="html">&lt;p&gt;Ktm: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article will describe, in short, what I have done to speed up queries which searches inside email bodies/attachments and RTFM articles which contain large amounts of [[WikiText]].&lt;br /&gt;
&lt;br /&gt;
There are 3 things that need to be done:&lt;br /&gt;
&lt;br /&gt;
* patch [[SearchBuilder]], to change LIKE &#039;search&#039; to @@ plainto_tsquery(&#039;search&#039;)&lt;br /&gt;
* add a column to hold the processed .content/.largecontent fields&lt;br /&gt;
* add [[PostgreSQL]] Text indexes for Attachments.content and [[ObjectCustomFieldValues]].largecontent&lt;br /&gt;
&lt;br /&gt;
Attached are the file needed todo this, that functionality isn&#039;t there anymore ;-( So here they come inline&lt;br /&gt;
&lt;br /&gt;
 This is the procedure that was followed to add full text&lt;br /&gt;
&lt;br /&gt;
search support to attachments and RTFM Largecontent fields.&lt;br /&gt;
&lt;br /&gt;
1. Patch [[SearchBuilder]].pm.&lt;br /&gt;
&lt;br /&gt;
2. Add a tsvector column to the attachements table to allow searching.&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;ALTER TABLE attachments ADD COLUMN textsearchable tsvector;&lt;br /&gt;
 UPDATE attachments SET textsearchable =&lt;br /&gt;
   to_tsvector(&#039;english&#039;, coalesce(subject,&#039;&#039;) || coalesce(content,&#039;&#039;));&lt;br /&gt;
 &lt;br /&gt;
 This first command failed with the error:&lt;br /&gt;
     ERROR:  string is too long for tsvector&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So I am adding the tsvectors only to those entries with a size &amp;amp;lt; 500KB:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;UPDATE attachments SET textsearchable = to_tsvector(&#039;english&#039;,&lt;br /&gt;
   substring(coalesce(subject,&#039;&#039;) || coalesce(content,&#039;&#039;), 1, 500000));&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Add the same text search column to objectcustomfieldvalues to index largecontent:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;ALTER TABLE objectcustomfieldvalues ADD COLUMN textsearchable tsvector;&lt;br /&gt;
 UPDATE objectcustomfieldvalues SET textsearchable = to_tsvector(&#039;english&#039;,&lt;br /&gt;
   substring(coalesce(largecontent,&#039;&#039;), 1, 500000));&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now add an index on the new column to speed up searches. Note, this can be either a GIST or GIN index. GIN is faster to search but larger and slower to update while GIST is slower to search but the index is smaller and faster to update -- pick your poison:&lt;br /&gt;
&lt;br /&gt;
 CREATE INDEX attachments_textsearch ON attachments&lt;br /&gt;
   USING gist(textsearchable);&lt;br /&gt;
 &lt;br /&gt;
 CREATE INDEX largecontent_textsearch ON objectcustomfieldvalues&lt;br /&gt;
   USING gist(textsearchable);&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Here are the index creation commands using the GIN index type. I have tried both, and unless you are in an extremely update intensive environment you will really want GIN -- very, very fast queries.&lt;br /&gt;
&lt;br /&gt;
 CREATE INDEX attachments_textsearch ON attachments&lt;br /&gt;
   USING GIN (textsearchable );&lt;br /&gt;
 &lt;br /&gt;
 CREATE INDEX largecontent_textsearch ON objectcustomfieldvalues&lt;br /&gt;
   USING GIN (textsearchable );&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Here is the patch to [[DBIx]]::[[SearchBuilder]] to add the [[PostgreSQL]] support:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;DBIx&amp;amp;gt; diff -u SearchBuilder.pm_*&lt;br /&gt;
 --- SearchBuilder.pm_ORIG       2009-01-28 09:13:38.000000000 -0600&lt;br /&gt;
 +++ SearchBuilder.pm_FULLTEXT   2009-02-01 15:36:52.000000000 -0600&lt;br /&gt;
 @@ -926,6 +926,22 @@&lt;br /&gt;
 &lt;br /&gt;
      }&lt;br /&gt;
 &lt;br /&gt;
 +    # Use FULLTEXT for large attachments.content and&lt;br /&gt;
 +    # objectcustomfieldvalues.largecontent in PostgreSQL.&lt;br /&gt;
 +    if (($QualifiedField =~ /Attachments_\d+\.Content/) or&lt;br /&gt;
 +        ($QualifiedField =~ /ObjectCustomFieldValues_\d+\.Largecontent/)) {&lt;br /&gt;
 +        if (($args{&#039;OPERATOR&#039;} eq &#039;LIKE&#039;) or ($args{&#039;OPERATOR&#039;} eq &#039;ILIKE&#039;)) {&lt;br /&gt;
 +            $QualifiedField =~ s/(?:Content|Largecontent)/textsearchable/i;&lt;br /&gt;
 +            $args{&#039;OPERATOR&#039;} = &#039;@@&#039;;&lt;br /&gt;
 +            $args{&#039;VALUE&#039;} = &amp;quot;plainto_tsquery($args{&#039;VALUE&#039;})&amp;quot;;&lt;br /&gt;
 +        }&lt;br /&gt;
 +        if (($args{&#039;OPERATOR&#039;} eq &#039;NOT LIKE&#039;) or ($args{&#039;OPERATOR&#039;} eq &#039;NOT ILIKE&#039;)) {&lt;br /&gt;
 +            $QualifiedField =~ s/(?:Content|Largecontent)/textsearchable/i;&lt;br /&gt;
 +            $args{&#039;OPERATOR&#039;} = &#039;@@&#039;;&lt;br /&gt;
 +            $args{&#039;VALUE&#039;} = &amp;quot;(!! plainto_tsquery($args{&#039;VALUE&#039;}))&amp;quot;;&lt;br /&gt;
 +        }&lt;br /&gt;
 +    }&lt;br /&gt;
 +&lt;br /&gt;
    my $clause = {&lt;br /&gt;
        field =&amp;amp;gt; $QualifiedField,&lt;br /&gt;
        op =&amp;amp;gt; $args{&#039;OPERATOR&#039;},&lt;br /&gt;
 &lt;br /&gt;
 --------------------------------------------------------------------&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A couple of comments about the approach used. I added a second column to hold the processed data. This is needed because there are certain conditions that cause FULL TEXT indexing to fail and it is easier to work around using a trigger to generate the tsvector column instead of having the index cause the INSERT to fail completely. In this case, it will not be indexed but these a pathological cases that really should not be searched anyway. The final piece is to setup a trigger to update the textsearchable column whenever the attachment.(subject/content) or objectcustomfieldvalues.largcontent are updated to keep the searching accurate.&lt;br /&gt;
&lt;br /&gt;
We also do not bother with stripping the &#039;%&#039; characters or the exit early tests in Handle.pm as the [[OracleText]] patches do. The reason is that the plainto_tsquery() will strip them for you so the basic patch is much simpler. Obviously, this search technique can be applied to any arbitrary field.&lt;br /&gt;
&lt;br /&gt;
Okay, here are the two triggers you need to keep the texsearchable columns updated when the attachments.subject/content or objectcustomfieldvalues.largecontent are changed:&lt;br /&gt;
&lt;br /&gt;
 CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE&lt;br /&gt;
 ON attachments FOR EACH ROW EXECUTE PROCEDURE&lt;br /&gt;
 tsvector_update_trigger(textsearchable, &#039;pg_catalog.english&#039;, subject, content);&lt;br /&gt;
 &lt;br /&gt;
 CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE&lt;br /&gt;
 ON objectcustomfieldvalues FOR EACH ROW EXECUTE PROCEDURE&lt;br /&gt;
 tsvector_update_trigger(textsearchable, &#039;pg_catalog.english&#039;, largecontent);&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
The set of triggers above will update the processed document column for every change. If you need more restricting updates, use something like the following which only processes the first 1/2MB of each attachment:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;CREATE FUNCTION attachments_trigger() RETURNS trigger AS $$&lt;br /&gt;
 begin&lt;br /&gt;
   new.textsearchable :=&lt;br /&gt;
       to_tsvector(&#039;pg_catalog.english&#039;, substring(coalesce(new.subject, &#039;&#039;) || coalesce(new.content, &#039;&#039;) from 1 for 500000));&lt;br /&gt;
   return new;&lt;br /&gt;
 end&lt;br /&gt;
 $$ LANGUAGE plpgsql;&lt;br /&gt;
 &lt;br /&gt;
 CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE&lt;br /&gt;
 ON attachments FOR EACH ROW EXECUTE PROCEDURE attachments_trigger();&lt;br /&gt;
 &lt;br /&gt;
 CREATE FUNCTION objectcustomfieldvalues_trigger() RETURNS trigger AS $$&lt;br /&gt;
 begin&lt;br /&gt;
   new.textsearchable :=&lt;br /&gt;
       to_tsvector(&#039;pg_catalog.english&#039;, substring(coalesce(new.largecontent, &#039;&#039;) from 1 for 500000));&lt;br /&gt;
   return new;&lt;br /&gt;
 end&lt;br /&gt;
 $$ LANGUAGE plpgsql;&lt;br /&gt;
 &lt;br /&gt;
 CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE&lt;br /&gt;
 ON objectcustomfieldvalues FOR EACH ROW EXECUTE PROCEDURE&lt;br /&gt;
 objectcustomfieldvalues_trigger();&lt;br /&gt;
 &lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Please send me any comments or feedback. --Ken&lt;/div&gt;</summary>
		<author><name>Ktm</name></author>
	</entry>
</feed>