[Rt-commit] rt branch, 3.9-fts, created. rt-3.9.4-30-gdceeb23

Ruslan Zakirov ruz at bestpractical.com
Thu Sep 30 22:17:48 EDT 2010


The branch, 3.9-fts has been created
        at  dceeb2377ab6721e11e09f17cf558e5811b20a31 (commit)

- Log -----------------------------------------------------------------
commit 240d3047398fd4d930f654ce6a54fb384eeef6ee
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Sep 30 04:33:48 2010 +0400

    JoinTransactions method
    
    based on fd0794e856331bd2f2d5f71534751d6e08e2057c

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 21102de..07cbeda 100755
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -85,6 +85,37 @@ sub _Init  {
     $self->SUPER::_Init( 'Handle' => $RT::Handle);
 }
 
+sub CleanSlate {
+    my $self = shift;
+    $self->{'_sql_aliases'} = {};
+    return $self->SUPER::CleanSlate(@_);
+}
+
+sub JoinTransactions {
+    my $self = shift;
+    my %args = ( New => 0, @_ );
+
+    return $self->{'_sql_aliases'}{'transactions'}
+        if !$args{'New'} && $self->{'_sql_aliases'}{'transactions'};
+
+    my $alias = $self->Join(
+        ALIAS1 => 'main',
+        FIELD1 => 'id',
+        TABLE2 => 'Transactions',
+        FIELD2 => 'ObjectId',
+    );
+    $self->Limit(
+        LEFTJOIN => $alias,
+        ALIAS    => $alias,
+        FIELD    => 'ObjectType',
+        VALUE    => ref $self->NewItem,
+    );
+    $self->{'_sql_aliases'}{'transactions'} = $alias
+        unless $args{'New'};
+
+    return $alias;
+}
+
 =head2 LimitToEnabled
 
 Only find items that haven't been disabled

commit e10464d8705f8db3c6412a31152db498532ec793
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Sep 30 04:39:33 2010 +0400

    use JoinTransactions

diff --git a/lib/RT/Tickets_Overlay.pm b/lib/RT/Tickets_Overlay.pm
index 113fc57..ec098f2 100755
--- a/lib/RT/Tickets_Overlay.pm
+++ b/lib/RT/Tickets_Overlay.pm
@@ -245,7 +245,6 @@ sub CleanSlate {
         _sql_group_members_aliases
         _sql_object_cfv_alias
         _sql_role_group_aliases
-        _sql_transalias
         _sql_trattachalias
         _sql_u_watchers_alias_for_sort
         _sql_u_watchers_aliases
@@ -622,20 +621,7 @@ sub _TransDateLimit {
 
     # See the comments for TransLimit, they apply here too
 
-    unless ( $sb->{_sql_transalias} ) {
-        $sb->{_sql_transalias} = $sb->Join(
-            ALIAS1 => 'main',
-            FIELD1 => 'id',
-            TABLE2 => 'Transactions',
-            FIELD2 => 'ObjectId',
-        );
-        $sb->SUPER::Limit(
-            ALIAS           => $sb->{_sql_transalias},
-            FIELD           => 'ObjectType',
-            VALUE           => 'RT::Ticket',
-            ENTRYAGGREGATOR => 'AND',
-        );
-    }
+    my $txn_alias = $sb->JoinTransactions;
 
     my $date = RT::Date->new( $sb->CurrentUser );
     $date->Set( Format => 'unknown', Value => $value );
@@ -653,7 +639,7 @@ sub _TransDateLimit {
         my $dayend = $date->ISO;
 
         $sb->_SQLLimit(
-            ALIAS         => $sb->{_sql_transalias},
+            ALIAS         => $txn_alias,
             FIELD         => 'Created',
             OPERATOR      => ">=",
             VALUE         => $daystart,
@@ -661,7 +647,7 @@ sub _TransDateLimit {
             @rest
         );
         $sb->_SQLLimit(
-            ALIAS         => $sb->{_sql_transalias},
+            ALIAS         => $txn_alias,
             FIELD         => 'Created',
             OPERATOR      => "<=",
             VALUE         => $dayend,
@@ -677,7 +663,7 @@ sub _TransDateLimit {
 
         #Search for the right field
         $sb->_SQLLimit(
-            ALIAS         => $sb->{_sql_transalias},
+            ALIAS         => $txn_alias,
             FIELD         => 'Created',
             OPERATOR      => $op,
             VALUE         => $date->ISO,
@@ -734,24 +720,11 @@ sub _TransLimit {
 
     my ( $self, $field, $op, $value, %rest ) = @_;
 
-    unless ( $self->{_sql_transalias} ) {
-        $self->{_sql_transalias} = $self->Join(
-            ALIAS1 => 'main',
-            FIELD1 => 'id',
-            TABLE2 => 'Transactions',
-            FIELD2 => 'ObjectId',
-        );
-        $self->SUPER::Limit(
-            ALIAS           => $self->{_sql_transalias},
-            FIELD           => 'ObjectType',
-            VALUE           => 'RT::Ticket',
-            ENTRYAGGREGATOR => 'AND',
-        );
-    }
+    my $txn_alias = $self->JoinTransactions;
     unless ( defined $self->{_sql_trattachalias} ) {
         $self->{_sql_trattachalias} = $self->_SQLJoin(
             TYPE   => 'LEFT', # not all txns have an attachment
-            ALIAS1 => $self->{_sql_transalias},
+            ALIAS1 => $txn_alias,
             FIELD1 => 'id',
             TABLE2 => 'Attachments',
             FIELD2 => 'TransactionId',

commit 377f37fdd9eb479d105592634741b0f82372fab1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Sep 30 05:00:38 2010 +0400

    factor _TransContentLimit method out of _TransLimit

diff --git a/lib/RT/Tickets_Overlay.pm b/lib/RT/Tickets_Overlay.pm
index ec098f2..0eec0ba 100755
--- a/lib/RT/Tickets_Overlay.pm
+++ b/lib/RT/Tickets_Overlay.pm
@@ -123,7 +123,7 @@ our %FIELD_METADATA = (
     LastUpdated      => [ 'DATE'            => 'LastUpdated', ], #loc_left_pair
     Created          => [ 'DATE'            => 'Created', ], #loc_left_pair
     Subject          => [ 'STRING', ], #loc_left_pair
-    Content          => [ 'TRANSFIELD', ], #loc_left_pair
+    Content          => [ 'TRANSCONTENT', ], #loc_left_pair
     ContentType      => [ 'TRANSFIELD', ], #loc_left_pair
     Filename         => [ 'TRANSFIELD', ], #loc_left_pair
     TransactionDate  => [ 'TRANSDATE', ], #loc_left_pair
@@ -156,6 +156,7 @@ our %dispatch = (
     DATE            => \&_DateLimit,
     STRING          => \&_StringLimit,
     TRANSFIELD      => \&_TransLimit,
+    TRANSCONTENT    => \&_TransContentLimit,
     TRANSDATE       => \&_TransDateLimit,
     WATCHERFIELD    => \&_WatcherLimit,
     MEMBERSHIPFIELD => \&_WatcherMembershipLimit,
@@ -677,16 +678,43 @@ sub _TransDateLimit {
 
 =head2 _TransLimit
 
-Limit based on the Content of a transaction or the ContentType.
-
-Meta Data:
-  none
+Limit based on the ContentType or the Filename of a transaction.
 
 =cut
 
 sub _TransLimit {
+    my ( $self, $field, $op, $value, %rest ) = @_;
+
+    my $txn_alias = $self->JoinTransactions;
+    unless ( defined $self->{_sql_trattachalias} ) {
+        $self->{_sql_trattachalias} = $self->_SQLJoin(
+            TYPE   => 'LEFT', # not all txns have an attachment
+            ALIAS1 => $txn_alias,
+            FIELD1 => 'id',
+            TABLE2 => 'Attachments',
+            FIELD2 => 'TransactionId',
+        );
+    }
+
+    $self->_SQLLimit(
+        %rest,
+        ALIAS         => $self->{_sql_trattachalias},
+        FIELD         => $field,
+        OPERATOR      => $op,
+        VALUE         => $value,
+        CASESENSITIVE => 0,
+    );
+}
+
+=head2 _TransContentLimit
+
+Limit based on the Content of a transaction.
+
+=cut
+
+sub _TransContentLimit {
 
-    # Content, ContentType, Filename
+    # Content search
 
     # If only this was this simple.  We've got to do something
     # complicated here:
@@ -732,36 +760,25 @@ sub _TransLimit {
     }
 
     #Search for the right field
-    if ( $field eq 'Content' and RT->Config->Get('DontSearchFileAttachments') ) {
-        $self->_OpenParen;
-        $self->_SQLLimit(
-			%rest,
-			ALIAS         => $self->{_sql_trattachalias},
-			FIELD         => $field,
-			OPERATOR      => $op,
-			VALUE         => $value,
-			CASESENSITIVE => 0,
-		       );
-        $self->_SQLLimit(
-			ENTRYAGGREGATOR => 'AND',
-			ALIAS           => $self->{_sql_trattachalias},
-			FIELD           => 'Filename',
-			OPERATOR        => 'IS',
-			VALUE           => 'NULL',
-		       );
-        $self->_CloseParen;
-    } else {
+    $self->_OpenParen;
+    $self->_SQLLimit(
+        %rest,
+        ALIAS         => $self->{_sql_trattachalias},
+        FIELD         => $field,
+        OPERATOR      => $op,
+        VALUE         => $value,
+        CASESENSITIVE => 0,
+    );
+    if ( RT->Config->Get('DontSearchFileAttachments') ) {
         $self->_SQLLimit(
-			%rest,
-			ALIAS         => $self->{_sql_trattachalias},
-			FIELD         => $field,
-			OPERATOR      => $op,
-			VALUE         => $value,
-			CASESENSITIVE => 0,
+            ENTRYAGGREGATOR => 'AND',
+            ALIAS           => $self->{_sql_trattachalias},
+            FIELD           => 'Filename',
+            OPERATOR        => 'IS',
+            VALUE           => 'NULL',
         );
     }
-
-
+    $self->_CloseParen;
 }
 
 =head2 _WatcherLimit

commit 5000a8ee8d0fa18b313822201cd00dea4c7421b6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Oct 1 04:55:10 2010 +0400

    FullTextSearch option

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 65c120f..b6d8153 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1533,6 +1533,26 @@ from being displayed in-line when viewing a ticket's history.
 
 Set($SuppressInlineTextFiles, undef);
 
+=item C<%FullTextSearch>
+
+Full text search (FTS) without indexes is a very slow operation and by
+default is disabled at all. To enable FTS set key 'Enable' to true value.
+
+Setup of indexes and filling them with data requires additional steps that
+vary from DB to DB. Use F<sbin/rt-setup-fulltext-index> helper
+for quick start. This script creates required structures in the DB and
+gives some ideas on next steps.
+
+=cut
+
+Set(%FullTextSearch,
+    Enable  => 0,
+    Indexed => 0,
+#    Table   => 'AttachmentsIndex',
+#    Column  => 'ftsindex',
+);
+
+
 =item C<$DontSearchFileAttachments>
 
 If C<$DontSearchFileAttachments> is set to a true value, then uploaded

commit be330a82da8c1fd6d6f6c571c3cc617ae1eeca1e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Oct 1 05:30:12 2010 +0400

    don't show content search if it's disabled

diff --git a/share/html/Elements/SelectAttachmentField b/share/html/Elements/SelectAttachmentField
index b81ebbe..220faed 100755
--- a/share/html/Elements/SelectAttachmentField
+++ b/share/html/Elements/SelectAttachmentField
@@ -47,7 +47,9 @@
 %# END BPS TAGGED BLOCK }}}
 <select name="<%$Name%>">
 <option value="Subject"><&|/l&>Subject</&></option>
+% if ( RT->Config->Get('FullTextSearch')->{'Enable'} ) {
 <option value="Content"><&|/l&>Content</&></option>
+% }
 <option value="ContentType"><&|/l&>Content-Type</&></option>
 <option value="Filename"><&|/l&>Filename</&></option>
 </select>

commit 90805dc5af43d9ef369b4524cdecc05e05c2a9f2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Oct 1 05:32:46 2010 +0400

    sbin/rt-fts-oracle

diff --git a/.gitignore b/.gitignore
index e5d60a9..484ff94 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,7 @@ sbin/rt-dump-database
 sbin/rt-email-dashboards
 sbin/rt-email-digest
 sbin/rt-email-group-admin
+sbin/rt-fts-oracle
 sbin/rt-server
 sbin/rt-session-viewer
 sbin/rt-setup-database
diff --git a/configure.ac b/configure.ac
index 9d4cad4..c6502f6 100755
--- a/configure.ac
+++ b/configure.ac
@@ -413,6 +413,7 @@ AC_CONFIG_FILES([
                  sbin/rt-validator
                  sbin/rt-email-group-admin
                  sbin/rt-server
+                 sbin/rt-fts-oracle
                  bin/fastcgi_server
                  bin/mason_handler.fcgi
                  bin/mason_handler.scgi
diff --git a/sbin/rt-fts-oracle.in b/sbin/rt-fts-oracle.in
new file mode 100755
index 0000000..d2a7bf2
--- /dev/null
+++ b/sbin/rt-fts-oracle.in
@@ -0,0 +1,420 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2010 Best Practical Solutions, LLC
+#                                          <jesse at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+use strict;
+use warnings;
+no warnings 'once';
+
+# fix lib paths, some may be relative
+BEGIN {
+    require File::Spec;
+    my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
+    my $bin_path;
+
+    for my $lib (@libs) {
+        unless ( File::Spec->file_name_is_absolute($lib) ) {
+            unless ($bin_path) {
+                if ( File::Spec->file_name_is_absolute(__FILE__) ) {
+                    $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
+                }
+                else {
+                    require FindBin;
+                    no warnings "once";
+                    $bin_path = $FindBin::Bin;
+                }
+            }
+            $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
+        }
+        unshift @INC, $lib;
+    }
+
+}
+
+=head1 NAME
+
+rt-fts-oracle - setup Oracle Text index for full text search
+
+=head1 USAGE
+
+    rt-fts-oracle --help
+    rt-fts-oracle --dba sysdba --dba-password 'secret'
+
+=head1 DESCRIPTION
+
+This utility grants CTXAPP role to RT's user and rights to execute functions
+from CTX_DDL package. Also, it creates several prefernces, functions and triggers
+all starting with 'rt_fts_' prefix in the name. After all an index is created.
+
+=cut
+
+use RT;
+RT::LoadConfig();
+
+my %DB = (
+    user           => $RT::DatabaseUser,
+    admin          => undef,
+    admin_password => undef,
+);
+
+my %OPT = (
+    help        => 0,
+);
+
+use Getopt::Long qw(GetOptions);
+GetOptions(
+    'h|help!'        => \$OPT{'help'},
+    'dba=s'          => \$DB{'admin'},
+    'dba-password=s' => \$DB{'admin_password'},
+);
+
+if ( $OPT{'help'} || !$DB{'admin'}) {
+    require Pod::Usage;
+    Pod::Usage::pod2usage(
+        -message => "",
+        -exitval => $OPT{'help'}? 0 : 1,
+        -verbose => 99,
+        -sections => $OPT{'help'}? 'NAME|USAGE|DESCRIPTION|OPTIONS' : 'NAME|USAGE',
+    );
+}
+
+RT::Init();
+
+{
+    my $dbah = dba_handle();
+    do_print_error( $dbah => 'GRANT CTXAPP TO '. $DB{'user'} );
+    do_print_error( $dbah => 'GRANT EXECUTE ON CTXSYS.CTX_DDL TO '. $DB{'user'} );
+}
+
+my $dbh = $RT::Handle->dbh;
+$dbh->{'RaiseError'} = 1;
+$dbh->{'PrintError'} = 1;
+
+my $prefix = 'rt_fts_';
+
+our %PREFERENCES = (
+    datastore => {
+        type => 'DIRECT_DATASTORE',
+    },
+    filter => {
+        type => 'AUTO_FILTER',
+#        attributes => {
+#            timeout => 120, # seconds
+#            timeout_type => 'HEURISTIC', # or 'FIXED'
+#        },
+    },
+    lexer => {
+        type => 'WORLD_LEXER',
+    },
+    word_list => {
+        type => 'BASIC_WORDLIST',
+        attributes => {
+            stemmer => 'AUTO',
+            fuzzy_match => 'AUTO',
+#            fuzzy_score => undef,
+#            fuzzy_numresults => undef,
+#            substring_index => undef,
+#            prefix_index => undef,
+#            prefix_length_min => undef,
+#            prefix_length_max => undef,
+#            wlidcard_maxterms => undef,
+        },
+    },
+    'section_group' => {
+        type => 'NULL_SECTION_GROUP',
+    },
+
+    storage => {
+        type => 'BASIC_STORAGE',
+        attributes => {
+            R_TABLE_CLAUSE => 'lob (data) store as (cache)',
+            I_INDEX_CLAUSE => 'compress 2',
+        },
+    },
+);
+
+my @params = ();
+push @params, create_datastore();
+push @params, create_filter();
+push @params, create_lexer();
+push @params, create_word_list();
+push @params, create_stop_list();
+push @params, create_section_group();
+push @params, create_storage();
+
+my $index_params = join "\n", @params;
+do_error_is_ok( $dbh => "DROP INDEX ${prefix}index" );
+$dbh->do(
+    "CREATE INDEX ${prefix}index ON Attachments(Content)
+    indextype is ctxsys.context parameters('
+        $index_params
+    ')",
+);
+
+sub create_datastore {
+    return sprintf 'datastore %s', create_preference(
+        %{ $PREFERENCES{'datastore'} },
+        name => 'datastore',
+    );
+}
+
+sub create_filter {
+    my $res = '';
+    $res .= sprintf "format column %s\n", create_format_column();
+    $res .= sprintf 'filter %s', create_preference(
+        %{ $PREFERENCES{'filter'} },
+        name => 'filter',
+    );
+    return $res;
+}
+
+sub create_lexer {
+    return sprintf 'lexer %s', create_preference(
+        %{ $PREFERENCES{'lexer'} },
+        name => 'lexer',
+    );
+}
+
+sub create_word_list {
+    return sprintf 'wordlist %s', create_preference(
+        %{ $PREFERENCES{'word_list'} },
+        name => 'word_list',
+    );
+}
+
+sub create_stop_list {
+    my $file = shift || 'etc/stopwords/en.txt';
+
+    my $name = $prefix .'stop_list';
+    do_error_is_ok( $dbh => 'begin ctx_ddl.drop_stoplist(?); end;', $name );
+    
+    $dbh->do(
+        'begin ctx_ddl.create_stoplist(?, ?);  end;',
+        undef, $name, 'BASIC_STOPLIST'
+    );
+
+    open my $fh, '<:utf8', $file
+        or die "couldn't open file '$file': $!";
+    while ( my $word = <$fh> ) {
+        chomp $word;
+        $dbh->do(
+            'begin ctx_ddl.add_stopword(?, ?); end;',
+            undef, $name, $word
+        );
+    }
+    close $fh;
+    return sprintf 'stoplist %s', $name;
+}
+
+sub create_section_group {
+    my $name = $prefix .'section_group';
+    do_error_is_ok( $dbh => 'begin ctx_ddl.drop_section_group(?); end;', $name );
+    $dbh->do(
+        'begin ctx_ddl.create_section_group(?, ?);  end;',
+        undef, $name, $PREFERENCES{'section_group'}{'type'}
+    );
+    return sprintf 'section group %s', $name;
+}
+
+sub create_storage {
+    return sprintf 'storage %s', create_preference(
+        %{ $PREFERENCES{'storage'} },
+        name => 'storage',
+    );
+}
+
+sub create_format_column {
+    my $column_name = 'ContentOracleFormat';
+    unless (
+        $dbh->column_info(
+            undef, undef, uc('Attachments'), uc( $column_name )
+        )->fetchrow_array
+    ) {
+        $dbh->do(qq{
+            ALTER TABLE Attachments ADD $column_name VARCHAR2(10)
+        });
+    }
+
+    my $detect_format = qq{
+        CREATE OR REPLACE FUNCTION ${prefix}detect_format_simple(
+            parent IN NUMBER,
+            type IN VARCHAR2,
+            encoding IN VARCHAR2,
+            fname IN VARCHAR2
+        )
+        RETURN VARCHAR2
+        AS
+            format VARCHAR2(10);
+        BEGIN
+            format := CASE
+    };
+    if ( $RT::DontSearchFileAttachments ) {
+        $detect_format .= qq{
+                WHEN fname IS NOT NULL THEN 'ignore'
+        };
+    }
+    my $binary = $RT::DontSearchBinaryAttachments? 'ignore' : 'binary';
+    $detect_format .= qq{
+                WHEN type = 'text' THEN 'text'
+                WHEN type = 'text/rtf' THEN '$binary'
+                WHEN type LIKE 'text/%' THEN 'text'
+                WHEN type LIKE 'message/%' THEN 'text'
+                WHEN type LIKE 'multipart/%' THEN 'ignore'
+                WHEN type LIKE 'image/%' THEN 'ignore'
+                WHEN type LIKE 'audio/%' THEN 'ignore'
+                WHEN type LIKE 'video/%' THEN 'ignore'
+                WHEN type LIKE '%signature%' THEN 'ignore'
+                WHEN type LIKE '%pkcs7%' THEN 'ignore'
+                WHEN type LIKE '%compress%' THEN 'ignore'
+                WHEN type LIKE '%zip%' THEN 'ignore'
+                WHEN type LIKE '%tar%' THEN 'ignore'
+                WHEN type LIKE '%/octet-stream' THEN 'ignore'
+                ELSE '$binary'
+            END;
+            RETURN format;
+        END;
+    };
+    create_procedure( $detect_format );
+
+    $dbh->do(qq{
+        UPDATE Attachments
+        SET $column_name = ${prefix}detect_format_simple(
+            Parent,
+            ContentType, ContentEncoding,
+            Filename
+        )
+        WHERE $column_name IS NULL
+    });
+    $dbh->do(qq{
+        CREATE OR REPLACE TRIGGER ${prefix}set_format
+        BEFORE INSERT
+        ON Attachments
+        FOR EACH ROW
+        BEGIN
+            :new.$column_name := ${prefix}detect_format_simple(
+                :new.Parent,
+                :new.ContentType, :new.ContentEncoding,
+                :new.Filename
+            );
+        END;
+    });
+    return $column_name;
+}
+
+sub create_preference {
+    my %info = @_;
+    my $name = $prefix . $info{'name'};
+    do_error_is_ok( $dbh => 'begin ctx_ddl.drop_preference(?); end;', $name );
+    $dbh->do(
+        'begin ctx_ddl.create_preference(?, ?);  end;',
+        undef, $name, $info{'type'}
+    );
+    return $name unless $info{'attributes'};
+
+    while ( my ($attr, $value) = each %{ $info{'attributes'} } ) {
+        $dbh->do(
+            'begin ctx_ddl.set_attribute(?, ?, ?);  end;',
+            undef, $name, $attr, $value
+        );
+    }
+
+    return $name;
+}
+
+sub create_procedure {
+    my $text = shift;
+
+    my $status = $dbh->do($text, { RaiseError => 0 });
+
+    # Statement succeeded
+    return if $status;
+
+    if ( 6550 != $dbh->err ) {
+        # Utter failure
+        die $dbh->errstr;
+    }
+    else {
+        my $msg = $dbh->func( 'plsql_errstr' );
+        die $dbh->errstr if !defined $msg;
+        die $msg if $msg;
+    }
+}
+
+sub dba_handle {
+    $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8";
+    $ENV{'NLS_NCHAR'} = "AL32UTF8";
+    my $dsn = do { my $h = new RT::Handle; $h->BuildDSN; $h->DSN };
+    my $dbh = DBI->connect(
+        $dsn, $DB{admin}, $DB{admin_password},
+        { RaiseError => 1, PrintError => 1 },
+    );
+    unless ( $dbh ) {
+        die "Failed to connect to $dsn as user '$DB{admin}': ". $DBI::errstr;
+    }
+    return $dbh;
+}
+
+sub do_error_is_ok {
+    my $dbh = shift;
+    local $dbh->{'RaiseError'} = 0;
+    local $dbh->{'PrintError'} = 0;
+    return $dbh->do(shift, undef, @_);
+}
+
+sub do_print_error {
+    my $dbh = shift;
+    local $dbh->{'RaiseError'} = 0;
+    local $dbh->{'PrintError'} = 1;
+    return $dbh->do(shift, undef, @_);
+}
+
+=head1 AUTHOR
+
+Ruslan Zakirov E<lt>ruz at bestpractical.comE<gt>
+
+=cut
+

commit dceeb2377ab6721e11e09f17cf558e5811b20a31
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Oct 1 06:19:46 2010 +0400

    FTS implementation for Pg and Oracle

diff --git a/lib/RT/Tickets_Overlay.pm b/lib/RT/Tickets_Overlay.pm
index 0eec0ba..25af445 100755
--- a/lib/RT/Tickets_Overlay.pm
+++ b/lib/RT/Tickets_Overlay.pm
@@ -748,6 +748,12 @@ sub _TransContentLimit {
 
     my ( $self, $field, $op, $value, %rest ) = @_;
 
+    my $config = RT->Config->Get('FullTextSearch') || {};
+    unless ( $config->{'Enable'} ) {
+        $self->_SQLLimit( %rest, FIELD => 'id', VALUE => 0 );
+        return;
+    }
+
     my $txn_alias = $self->JoinTransactions;
     unless ( defined $self->{_sql_trattachalias} ) {
         $self->{_sql_trattachalias} = $self->_SQLJoin(
@@ -759,16 +765,73 @@ sub _TransContentLimit {
         );
     }
 
-    #Search for the right field
     $self->_OpenParen;
-    $self->_SQLLimit(
-        %rest,
-        ALIAS         => $self->{_sql_trattachalias},
-        FIELD         => $field,
-        OPERATOR      => $op,
-        VALUE         => $value,
-        CASESENSITIVE => 0,
-    );
+    if ( $config->{'Indexed'} ) {
+        my $db_type = RT->Config->Get('DatabaseType');
+
+        my $alias;
+        if ( $config->{'Table'} ) {
+            $alias = $self->{'_sql_aliases'}{'full_text'} ||= $self->_SQLJoin(
+                TYPE   => 'LEFT',
+                ALIAS1 => $self->{'_sql_trattachalias'},
+                FIELD1 => 'id',
+                TABLE2 => $config->{'Table'},
+                FIELD2 => 'id',
+            );
+        } else {
+            $alias = $self->{'_sql_trattachalias'};
+        }
+
+        my $field = $config->{'Field'} || 'Content';
+        if ( $db_type eq 'Oracle' ) {
+            my $dbh = $RT::Handle->dbh;
+            $self->_SQLLimit(
+                %rest,
+                # XXX: Nasty hack
+                ALIAS         => 'CONTAINS( '. $self->{_sql_trattachalias},
+                FIELD         => $field . ', '. $dbh->quote($value) .')',
+                OPERATOR      => '>',
+                VALUE         => 0,
+                QUOTEVALUE    => 0,
+                CASESENSITIVE => 1,
+            );
+            # this is required to trick DBIx::SB's LEFT JOINS optimizer
+            # into deciding that join is redundant as it is
+            $self->_SQLLimit(
+                ENTRYAGGREGATOR => 'AND',
+                ALIAS           => $self->{_sql_trattachalias},
+                FIELD           => 'Content',
+                OPERATOR        => 'IS NOT',
+                VALUE           => 'NULL',
+            );
+        }
+        elsif ( $db_type eq 'Pg' ) {
+            my $dbh = $RT::Handle->dbh;
+            #XXX: handle negative searches
+            $self->_SQLLimit(
+                %rest,
+                ALIAS       => $alias,
+                FIELD       => $field,
+                OPERATOR    => '@@',
+                VALUE       => 'plainto_tsquery('. $dbh->quote($value) .')',
+                QUOTEVALUE  => 0,
+            );
+        }
+        else {
+            $RT::Logger->error( "Indexed full text search is not supported for $db_type" );
+            $self->_SQLLimit( %rest, FIELD => 'id', VALUE => 0 );
+            return;
+        }
+    } else {
+        $self->_SQLLimit(
+            %rest,
+            ALIAS         => $self->{_sql_trattachalias},
+            FIELD         => $field,
+            OPERATOR      => $op,
+            VALUE         => $value,
+            CASESENSITIVE => 0,
+        );
+    }
     if ( RT->Config->Get('DontSearchFileAttachments') ) {
         $self->_SQLLimit(
             ENTRYAGGREGATOR => 'AND',

-----------------------------------------------------------------------


More information about the Rt-commit mailing list