Merge branch 'master' of https://github.com/jgoodman/Freeside
[freeside.git] / rt / lib / RT / Handle.pm
index 46070ce..4ea1576 100644 (file)
@@ -1,40 +1,40 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # BEGIN BPS TAGGED BLOCK {{{
-# 
+#
 # COPYRIGHT:
 # COPYRIGHT:
-# 
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-#                                          <jesse@bestpractical.com>
-# 
+#
+# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+#                                          <sales@bestpractical.com>
+#
 # (Except where explicitly superseded by other copyright notices)
 # (Except where explicitly superseded by other copyright notices)
-# 
-# 
+#
+#
 # LICENSE:
 # 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 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.
 # 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.
 # 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:
 # 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.)
 # (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
 # 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
@@ -43,7 +43,7 @@
 # royalty-free, perpetual, license to use, copy, create derivative
 # works based on those contributions, and sublicense and distribute
 # those contributions and any derivatives thereof.
 # 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 }}}
 
 =head1 NAME
 # END BPS TAGGED BLOCK }}}
 
 =head1 NAME
@@ -70,7 +70,8 @@ package RT::Handle;
 
 use strict;
 use warnings;
 
 use strict;
 use warnings;
-use vars qw/@ISA/;
+
+use File::Spec;
 
 =head1 METHODS
 
 
 =head1 METHODS
 
@@ -82,8 +83,9 @@ L<DBIx::SearchBuilder::Handle>, using the C<DatabaseType> configuration.
 =cut
 
 sub FinalizeDatabaseType {
 =cut
 
 sub FinalizeDatabaseType {
-    eval "use DBIx::SearchBuilder::Handle::". RT->Config->Get('DatabaseType') .";
-    \@ISA= qw(DBIx::SearchBuilder::Handle::". RT->Config->Get('DatabaseType') .");";
+    eval {
+        use base "DBIx::SearchBuilder::Handle::". RT->Config->Get('DatabaseType');
+    };
 
     if ($@) {
         die "Unable to load DBIx::SearchBuilder database handle for '". RT->Config->Get('DatabaseType') ."'.\n".
 
     if ($@) {
         die "Unable to load DBIx::SearchBuilder database handle for '". RT->Config->Get('DatabaseType') ."'.\n".
@@ -101,6 +103,7 @@ Takes nothing.
 
 sub Connect {
     my $self = shift;
 
 sub Connect {
     my $self = shift;
+    my %args = (@_);
 
     my $db_type = RT->Config->Get('DatabaseType');
     if ( $db_type eq 'Oracle' ) {
 
     my $db_type = RT->Config->Get('DatabaseType');
     if ( $db_type eq 'Oracle' ) {
@@ -111,6 +114,8 @@ sub Connect {
     $self->SUPER::Connect(
         User => RT->Config->Get('DatabaseUser'),
         Password => RT->Config->Get('DatabasePassword'),
     $self->SUPER::Connect(
         User => RT->Config->Get('DatabaseUser'),
         Password => RT->Config->Get('DatabasePassword'),
+        DisconnectHandleOnDestroy => 1,
+        %args,
     );
 
     if ( $db_type eq 'mysql' ) {
     );
 
     if ( $db_type eq 'mysql' ) {
@@ -119,6 +124,15 @@ sub Connect {
         $self->dbh->do("SET NAMES 'utf8'") if $version >= 4.1;
     }
 
         $self->dbh->do("SET NAMES 'utf8'") if $version >= 4.1;
     }
 
+
+    if ( $db_type eq 'Pg' ) {
+        my $version = $self->DatabaseVersion;
+        ($version) = $version =~ /^(\d+\.\d+)/;
+        $self->dbh->do("SET bytea_output = 'escape'") if $version >= 9.0;
+    }
+
+
+
     $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
 }
 
     $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
 }
 
@@ -129,7 +143,6 @@ from the config.
 
 =cut
 
 
 =cut
 
-require File::Spec;
 
 sub BuildDSN {
     my $self = shift;
 
 sub BuildDSN {
     my $self = shift;
@@ -149,7 +162,6 @@ sub BuildDSN {
         Port       => $db_port,
         Driver     => $db_type,
         RequireSSL => RT->Config->Get('DatabaseRequireSSL'),
         Port       => $db_port,
         Driver     => $db_type,
         RequireSSL => RT->Config->Get('DatabaseRequireSSL'),
-        DisconnectHandleOnDestroy => 1,
     );
     if ( $db_type eq 'Oracle' && $db_host ) {
         $args{'SID'} = delete $args{'Database'};
     );
     if ( $db_type eq 'Oracle' && $db_host ) {
         $args{'SID'} = delete $args{'Database'};
@@ -201,10 +213,6 @@ sub SystemDSN {
         # with postgres, you want to connect to template1 database
         $dsn =~ s/dbname=\Q$db_name/dbname=template1/;
     }
         # with postgres, you want to connect to template1 database
         $dsn =~ s/dbname=\Q$db_name/dbname=template1/;
     }
-    elsif ( $db_type eq 'Informix' ) {
-        # with Informix, you want to connect sans database:
-        $dsn =~ s/Informix:\Q$db_name/Informix:/;
-    }
     return $dsn;
 }
 
     return $dsn;
 }
 
@@ -216,36 +224,29 @@ sub SystemDSN {
 
 sub CheckIntegrity {
     my $self = shift;
 
 sub CheckIntegrity {
     my $self = shift;
-    
-    my $dsn = $self->DSN;
-    my $user = RT->Config->Get('DatabaseUser');
-    my $pass = RT->Config->Get('DatabasePassword');
+    $self = new $self unless ref $self;
 
 
-    my $dbh = DBI->connect(
-        $dsn, $user, $pass,
-        { RaiseError => 0, PrintError => 0 },
-    );
-    unless ( $dbh ) {
-        return (0, 'no connection', "Failed to connect to $dsn as user '$user': ". $DBI::errstr);
+    unless ($RT::Handle and $RT::Handle->dbh) {
+        local $@;
+        unless ( eval { RT::ConnectToDatabase(); 1 } ) {
+            return (0, 'no connection', "$@");
+        }
     }
 
     }
 
-    RT::ConnectToDatabase();
-    RT::InitLogging();
-
     require RT::CurrentUser;
     require RT::CurrentUser;
-    my $test_user = new RT::CurrentUser;
+    my $test_user = RT::CurrentUser->new;
     $test_user->Load('RT_System');
     unless ( $test_user->id ) {
     $test_user->Load('RT_System');
     unless ( $test_user->id ) {
-        return (0, 'no system user', "Couldn't find RT_System user in the DB '$dsn'");
+        return (0, 'no system user', "Couldn't find RT_System user in the DB '". $self->DSN ."'");
     }
 
     }
 
-    $test_user = new RT::CurrentUser;
+    $test_user = RT::CurrentUser->new;
     $test_user->Load('Nobody');
     unless ( $test_user->id ) {
     $test_user->Load('Nobody');
     unless ( $test_user->id ) {
-        return (0, 'no nobody user', "Couldn't find Nobody user in the DB '$dsn'");
+        return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $self->DSN ."'");
     }
 
     }
 
-    return $dbh;
+    return $RT::Handle->dbh;
 }
 
 sub CheckCompatibility {
 }
 
 sub CheckCompatibility {
@@ -261,17 +262,19 @@ sub CheckCompatibility {
             unless $version;
 
         ($version) = $version =~ /^(\d+\.\d+)/;
             unless $version;
 
         ($version) = $version =~ /^(\d+\.\d+)/;
-        return (0, "RT is unsupported on MySQL versions before 4.0.x, it's $version")
-            if $version < 4;
+        return (0, "RT is unsupported on MySQL versions before 4.1.  Your version is $version.")
+            if $version < 4.1;
 
         # MySQL must have InnoDB support
 
         # MySQL must have InnoDB support
-        my $innodb = ($dbh->selectrow_array("show variables like 'have_innodb'"))[1];
-        if ( lc $innodb eq "no" ) {
+        local $dbh->{FetchHashKeyName} = 'NAME_lc';
+        my $innodb = lc($dbh->selectall_hashref("SHOW ENGINES", "engine")->{InnoDB}{support} || "no");
+        if ( $innodb eq "no" ) {
             return (0, "RT requires that MySQL be compiled with InnoDB table support.\n".
             return (0, "RT requires that MySQL be compiled with InnoDB table support.\n".
-                "See http://dev.mysql.com/doc/mysql/en/InnoDB.html");
-        } elsif ( lc $innodb eq "disabled" ) {
+                "See <http://dev.mysql.com/doc/mysql/en/innodb-storage-engine.html>\n".
+                "and check that there are no 'skip-innodb' lines in your my.cnf.");
+        } elsif ( $innodb eq "disabled" ) {
             return (0, "RT requires that MySQL InnoDB table support be enabled.\n".
             return (0, "RT requires that MySQL InnoDB table support be enabled.\n".
-                "Remove the 'skip-innodb' line from your my.cnf file, restart MySQL, and try again.\n");
+                "Remove the 'skip-innodb' or 'innodb = OFF' line from your my.cnf file, restart MySQL, and try again.\n");
         }
 
         if ( $state eq 'post' ) {
         }
 
         if ( $state eq 'post' ) {
@@ -279,18 +282,38 @@ sub CheckCompatibility {
             unless ( $create_table =~ /(?:ENGINE|TYPE)\s*=\s*InnoDB/i ) {
                 return (0, "RT requires that all its tables be of InnoDB type. Upgrade RT tables.");
             }
             unless ( $create_table =~ /(?:ENGINE|TYPE)\s*=\s*InnoDB/i ) {
                 return (0, "RT requires that all its tables be of InnoDB type. Upgrade RT tables.");
             }
-        }
-        if ( $version >= 4.1 && $state eq 'post' ) {
-            my $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Attachments")->[1];
+
+            $create_table = $dbh->selectrow_arrayref("SHOW CREATE TABLE Attachments")->[1];
             unless ( $create_table =~ /\bContent\b[^,]*BLOB/i ) {
                 return (0, "RT since version 3.8 has new schema for MySQL versions after 4.1.0\n"
                     ."Follow instructions in the UPGRADING.mysql file.");
             }
         }
             unless ( $create_table =~ /\bContent\b[^,]*BLOB/i ) {
                 return (0, "RT since version 3.8 has new schema for MySQL versions after 4.1.0\n"
                     ."Follow instructions in the UPGRADING.mysql file.");
             }
         }
+
+        my $max_packet = ($dbh->selectrow_array("show variables like 'max_allowed_packet'"))[1];
+        if ($state =~ /^(create|post)$/ and $max_packet <= (1024 * 1024)) {
+            my $max_packet = sprintf("%.1fM", $max_packet/1024/1024);
+            warn "max_allowed_packet is set to $max_packet, which limits the maximum attachment or email size that RT can process.  Consider adjusting MySQL's max_allowed_packet setting.\n";
+        }
     }
     return (1)
 }
 
     }
     return (1)
 }
 
+sub CheckSphinxSE {
+    my $self = shift;
+
+    my $dbh = $RT::Handle->dbh;
+    local $dbh->{'RaiseError'} = 0;
+    local $dbh->{'PrintError'} = 0;
+    my $has = ($dbh->selectrow_array("show variables like 'have_sphinx'"))[1];
+    $has ||= ($dbh->selectrow_array(
+        "select 'yes' from INFORMATION_SCHEMA.PLUGINS where PLUGIN_NAME = 'sphinx' AND PLUGIN_STATUS='active'"
+    ))[0];
+
+    return 0 unless lc($has||'') eq "yes";
+    return 1;
+}
+
 =head2 Database maintanance
 
 =head3 CreateDatabase $DBH
 =head2 Database maintanance
 
 =head3 CreateDatabase $DBH
@@ -336,14 +359,10 @@ sub CreateDatabase {
         return (1, "Created user $db_user. All RT's objects should be in his schema.");
     }
     elsif ( $db_type eq 'Pg' ) {
         return (1, "Created user $db_user. All RT's objects should be in his schema.");
     }
     elsif ( $db_type eq 'Pg' ) {
-        # XXX: as we get external DBH we don't know if RaiseError or PrintError
-        # are enabled, so we have to setup it here and restore them back
-        $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0")
-            || $dbh->do("CREATE DATABASE $db_name TEMPLATE template0");
+        $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0");
     }
     }
-    elsif ( $db_type eq 'Informix' ) {
-        local $ENV{'DB_LOCALE'} = 'en_us.utf8';
-        $status = $dbh->do("CREATE DATABASE $db_name WITH BUFFERED LOG");
+    elsif ( $db_type eq 'mysql' ) {
+        $status = $dbh->do("CREATE DATABASE $db_name DEFAULT CHARACTER SET utf8");
     }
     else {
         $status = $dbh->do("CREATE DATABASE $db_name");
     }
     else {
         $status = $dbh->do("CREATE DATABASE $db_name");
@@ -351,15 +370,15 @@ sub CreateDatabase {
     return ($status, $DBI::errstr);
 }
 
     return ($status, $DBI::errstr);
 }
 
-=head3 DropDatabase $DBH [Force => 0]
+=head3 DropDatabase $DBH
 
 Drops RT's database. This method can be used as class method.
 
 Takes DBI handle as first argument. Many database systems require
 
 Drops RT's database. This method can be used as class method.
 
 Takes DBI handle as first argument. Many database systems require
-special handle to allow you to create a new database, so you have
-to use L<SystemDSN> method during connection.
+a special handle to allow you to drop a database, so you may have
+to use L<SystemDSN> when acquiring the DBI handle.
 
 
-Fetches type and name of the DB from the config.
+Fetches the type and name of the database from the config.
 
 =cut
 
 
 =cut
 
@@ -370,7 +389,7 @@ sub DropDatabase {
     my $db_type = RT->Config->Get('DatabaseType');
     my $db_name = RT->Config->Get('DatabaseName');
 
     my $db_type = RT->Config->Get('DatabaseType');
     my $db_name = RT->Config->Get('DatabaseName');
 
-    if ( $db_type eq 'Oracle' || $db_type eq 'Informix' ) {
+    if ( $db_type eq 'Oracle' ) {
         my $db_user = RT->Config->Get('DatabaseUser');
         my $status = $dbh->do( "DROP USER $db_user CASCADE" );
         unless ( $status ) {
         my $db_user = RT->Config->Get('DatabaseUser');
         my $status = $dbh->do( "DROP USER $db_user CASCADE" );
         unless ( $status ) {
@@ -465,10 +484,10 @@ sub InsertSchema {
 
     my (@schema);
 
 
     my (@schema);
 
-    open my $fh_schema, "<$file";
+    open( my $fh_schema, '<', $file ) or die $!;
 
     my $has_local = 0;
 
     my $has_local = 0;
-    open my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type )
+    open( my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type ))
         and $has_local = 1;
 
     my $statement = "";
         and $has_local = 1;
 
     my $statement = "";
@@ -547,17 +566,34 @@ sub GetVersionFile {
     return defined $version? $version{ $version } : undef;
 }
 
     return defined $version? $version{ $version } : undef;
 }
 
+{ my %word = (
+    a     => -4,
+    alpha => -4,
+    b     => -3,
+    beta  => -3,
+    pre   => -2,
+    rc    => -1,
+    head  => 9999,
+);
 sub cmp_version($$) {
     my ($a, $b) = (@_);
 sub cmp_version($$) {
     my ($a, $b) = (@_);
-    $b =~ s/HEAD$/9999/;
-    my @a = split /[^0-9]+/, $a;
-    my @b = split /[^0-9]+/, $b;
+    my @a = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
+        split /([^0-9]+)/, $a;
+    my @b = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
+        split /([^0-9]+)/, $b;
+    @a > @b
+        ? push @b, (0) x (@a-@b)
+        : push @a, (0) x (@b-@a);
     for ( my $i = 0; $i < @a; $i++ ) {
     for ( my $i = 0; $i < @a; $i++ ) {
-        return 1 unless defined $b[$i];
         return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
     }
         return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
     }
-    return 0 if @a == @b;
-    return -1;
+    return 0;
+}
+
+sub version_words {
+    return keys %word;
+}
+
 }
 
 
 }
 
 
@@ -583,13 +619,13 @@ sub InsertInitialData {
     {
         require RT::CurrentUser;
 
     {
         require RT::CurrentUser;
 
-        my $test_user = RT::User->new( new RT::CurrentUser );
+        my $test_user = RT::User->new( RT::CurrentUser->new() );
         $test_user->Load('RT_System');
         if ( $test_user->id ) {
             push @warns, "Found system user in the DB.";
         }
         else {
         $test_user->Load('RT_System');
         if ( $test_user->id ) {
             push @warns, "Found system user in the DB.";
         }
         else {
-            my $user = RT::User->new( new RT::CurrentUser );
+            my $user = RT::User->new( RT::CurrentUser->new() );
             my ( $val, $msg ) = $user->_BootstrapCreate(
                 Name     => 'RT_System',
                 RealName => 'The RT System itself',
             my ( $val, $msg ) = $user->_BootstrapCreate(
                 Name     => 'RT_System',
                 RealName => 'The RT System itself',
@@ -605,15 +641,15 @@ sub InsertInitialData {
 
     # init RT::SystemUser and RT::System objects
     RT::InitSystemObjects();
 
     # init RT::SystemUser and RT::System objects
     RT::InitSystemObjects();
-    unless ( $RT::SystemUser->id ) {
+    unless ( RT->SystemUser->id ) {
         return (0, "Couldn't load system user");
     }
 
     # grant SuperUser right to system user
     {
         return (0, "Couldn't load system user");
     }
 
     # grant SuperUser right to system user
     {
-        my $test_ace = RT::ACE->new( $RT::SystemUser );
+        my $test_ace = RT::ACE->new( RT->SystemUser );
         $test_ace->LoadByCols(
         $test_ace->LoadByCols(
-            PrincipalId   => ACLEquivGroupId( $RT::SystemUser->Id ),
+            PrincipalId   => ACLEquivGroupId( RT->SystemUser->Id ),
             PrincipalType => 'Group',
             RightName     => 'SuperUser',
             ObjectType    => 'RT::System',
             PrincipalType => 'Group',
             RightName     => 'SuperUser',
             ObjectType    => 'RT::System',
@@ -622,9 +658,9 @@ sub InsertInitialData {
         if ( $test_ace->id ) {
             push @warns, "System user has global SuperUser right.";
         } else {
         if ( $test_ace->id ) {
             push @warns, "System user has global SuperUser right.";
         } else {
-            my $ace = RT::ACE->new( $RT::SystemUser );
+            my $ace = RT::ACE->new( RT->SystemUser );
             my ( $val, $msg ) = $ace->_BootstrapCreate(
             my ( $val, $msg ) = $ace->_BootstrapCreate(
-                PrincipalId   => ACLEquivGroupId( $RT::SystemUser->Id ),
+                PrincipalId   => ACLEquivGroupId( RT->SystemUser->Id ),
                 PrincipalType => 'Group',
                 RightName     => 'SuperUser',
                 ObjectType    => 'RT::System',
                 PrincipalType => 'Group',
                 RightName     => 'SuperUser',
                 ObjectType    => 'RT::System',
@@ -640,14 +676,14 @@ sub InsertInitialData {
     # $self->loc('Privileged'); # For the string extractor to get a string to localize
     # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
     foreach my $name (qw(Everyone Privileged Unprivileged)) {
     # $self->loc('Privileged'); # For the string extractor to get a string to localize
     # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
     foreach my $name (qw(Everyone Privileged Unprivileged)) {
-        my $group = RT::Group->new( $RT::SystemUser );
+        my $group = RT::Group->new( RT->SystemUser );
         $group->LoadSystemInternalGroup( $name );
         if ( $group->id ) {
             push @warns, "System group '$name' already exists.";
             next;
         }
 
         $group->LoadSystemInternalGroup( $name );
         if ( $group->id ) {
             push @warns, "System group '$name' already exists.";
             next;
         }
 
-        $group = RT::Group->new( $RT::SystemUser );
+        $group = RT::Group->new( RT->SystemUser );
         my ( $val, $msg ) = $group->_Create(
             Type        => $name,
             Domain      => 'SystemInternal',
         my ( $val, $msg ) = $group->_Create(
             Type        => $name,
             Domain      => 'SystemInternal',
@@ -660,7 +696,7 @@ sub InsertInitialData {
 
     # nobody
     {
 
     # nobody
     {
-        my $user = RT::User->new( $RT::SystemUser );
+        my $user = RT::User->new( RT->SystemUser );
         $user->Load('Nobody');
         if ( $user->id ) {
             push @warns, "Found 'Nobody' user in the DB.";
         $user->Load('Nobody');
         if ( $user->id ) {
             push @warns, "Found 'Nobody' user in the DB.";
@@ -692,14 +728,14 @@ sub InsertInitialData {
 
     # system role groups
     foreach my $name (qw(Owner Requestor Cc AdminCc)) {
 
     # system role groups
     foreach my $name (qw(Owner Requestor Cc AdminCc)) {
-        my $group = RT::Group->new( $RT::SystemUser );
+        my $group = RT::Group->new( RT->SystemUser );
         $group->LoadSystemRoleGroup( $name );
         if ( $group->id ) {
             push @warns, "System role '$name' already exists.";
             next;
         }
 
         $group->LoadSystemRoleGroup( $name );
         if ( $group->id ) {
             push @warns, "System role '$name' already exists.";
             next;
         }
 
-        $group = RT::Group->new( $RT::SystemUser );
+        $group = RT::Group->new( RT->SystemUser );
         my ( $val, $msg ) = $group->_Create(
             Type        => $name,
             Domain      => 'RT::System-Role',
         my ( $val, $msg ) = $group->_Create(
             Type        => $name,
             Domain      => 'RT::System-Role',
@@ -725,6 +761,11 @@ Load some sort of data into the database, takes path to a file.
 sub InsertData {
     my $self     = shift;
     my $datafile = shift;
 sub InsertData {
     my $self     = shift;
     my $datafile = shift;
+    my $root_password = shift;
+    my %args     = (
+        disconnect_after => 1,
+        @_
+    );
 
     # Slurp in stuff to insert from the datafile. Possible things to go in here:-
     our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
 
     # Slurp in stuff to insert from the datafile. Possible things to go in here:-
     our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
@@ -748,7 +789,7 @@ sub InsertData {
     if ( @Groups ) {
         $RT::Logger->debug("Creating groups...");
         foreach my $item (@Groups) {
     if ( @Groups ) {
         $RT::Logger->debug("Creating groups...");
         foreach my $item (@Groups) {
-            my $new_entry = RT::Group->new( $RT::SystemUser );
+            my $new_entry = RT::Group->new( RT->SystemUser );
             my $member_of = delete $item->{'MemberOf'};
             my ( $return, $msg ) = $new_entry->_Create(%$item);
             unless ( $return ) {
             my $member_of = delete $item->{'MemberOf'};
             my ( $return, $msg ) = $new_entry->_Create(%$item);
             unless ( $return ) {
@@ -760,7 +801,7 @@ sub InsertData {
             if ( $member_of ) {
                 $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
                 foreach( @$member_of ) {
             if ( $member_of ) {
                 $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
                 foreach( @$member_of ) {
-                    my $parent = RT::Group->new($RT::SystemUser);
+                    my $parent = RT::Group->new(RT->SystemUser);
                     if ( ref $_ eq 'HASH' ) {
                         $parent->LoadByCols( %$_ );
                     }
                     if ( ref $_ eq 'HASH' ) {
                         $parent->LoadByCols( %$_ );
                     }
@@ -794,7 +835,10 @@ sub InsertData {
     if ( @Users ) {
         $RT::Logger->debug("Creating users...");
         foreach my $item (@Users) {
     if ( @Users ) {
         $RT::Logger->debug("Creating users...");
         foreach my $item (@Users) {
-            my $new_entry = new RT::User( $RT::SystemUser );
+            if ( $item->{'Name'} eq 'root' && $root_password ) {
+                $item->{'Password'} = $root_password;
+            }
+            my $new_entry = RT::User->new( RT->SystemUser );
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
@@ -807,7 +851,7 @@ sub InsertData {
     if ( @Queues ) {
         $RT::Logger->debug("Creating queues...");
         for my $item (@Queues) {
     if ( @Queues ) {
         $RT::Logger->debug("Creating queues...");
         for my $item (@Queues) {
-            my $new_entry = new RT::Queue($RT::SystemUser);
+            my $new_entry = RT::Queue->new(RT->SystemUser);
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
@@ -820,16 +864,36 @@ sub InsertData {
     if ( @CustomFields ) {
         $RT::Logger->debug("Creating custom fields...");
         for my $item ( @CustomFields ) {
     if ( @CustomFields ) {
         $RT::Logger->debug("Creating custom fields...");
         for my $item ( @CustomFields ) {
-            my $new_entry = new RT::CustomField( $RT::SystemUser );
+            my $new_entry = RT::CustomField->new( RT->SystemUser );
             my $values    = delete $item->{'Values'};
 
             my @queues;
             # if ref then it's list of queues, so we do things ourself
             if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
             my $values    = delete $item->{'Values'};
 
             my @queues;
             # if ref then it's list of queues, so we do things ourself
             if ( exists $item->{'Queue'} && ref $item->{'Queue'} ) {
-                $item->{'LookupType'} = 'RT::Queue-RT::Ticket';
+                $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket';
                 @queues = @{ delete $item->{'Queue'} };
             }
 
                 @queues = @{ delete $item->{'Queue'} };
             }
 
+            if ( $item->{'BasedOn'} ) {
+                if ( $item->{'BasedOn'} =~ /^\d+$/) {
+                    # Already have an ID -- should be fine
+                } elsif ( $item->{'LookupType'} ) {
+                    my $basedon = RT::CustomField->new($RT::SystemUser);
+                    my ($ok, $msg ) = $basedon->LoadByCols( Name => $item->{'BasedOn'},
+                                                            LookupType => $item->{'LookupType'} );
+                    if ($ok) {
+                        $item->{'BasedOn'} = $basedon->Id;
+                    } else {
+                        $RT::Logger->error("Unable to load $item->{BasedOn} as a $item->{LookupType} CF.  Skipping BasedOn: $msg");
+                        delete $item->{'BasedOn'};
+                    }
+                } else {
+                    $RT::Logger->error("Unable to load CF $item->{BasedOn} because no LookupType was specified.  Skipping BasedOn");
+                    delete $item->{'BasedOn'};
+                }
+
+            } 
+
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless( $return ) {
                 $RT::Logger->error( $msg );
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless( $return ) {
                 $RT::Logger->error( $msg );
@@ -843,18 +907,18 @@ sub InsertData {
 
             # apply by default
             if ( !@queues && !exists $item->{'Queue'} && $item->{LookupType} ) {
 
             # apply by default
             if ( !@queues && !exists $item->{'Queue'} && $item->{LookupType} ) {
-                my $ocf = RT::ObjectCustomField->new($RT::SystemUser);
+                my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
                 $ocf->Create( CustomField => $new_entry->Id );
             }
 
             for my $q (@queues) {
                 $ocf->Create( CustomField => $new_entry->Id );
             }
 
             for my $q (@queues) {
-                my $q_obj = RT::Queue->new($RT::SystemUser);
+                my $q_obj = RT::Queue->new(RT->SystemUser);
                 $q_obj->Load($q);
                 unless ( $q_obj->Id ) {
                     $RT::Logger->error("Could not find queue ". $q );
                     next;
                 }
                 $q_obj->Load($q);
                 unless ( $q_obj->Id ) {
                     $RT::Logger->error("Could not find queue ". $q );
                     next;
                 }
-                my $OCF = RT::ObjectCustomField->new($RT::SystemUser);
+                my $OCF = RT::ObjectCustomField->new(RT->SystemUser);
                 ( $return, $msg ) = $OCF->Create(
                     CustomField => $new_entry->Id,
                     ObjectId    => $q_obj->Id,
                 ( $return, $msg ) = $OCF->Create(
                     CustomField => $new_entry->Id,
                     ObjectId    => $q_obj->Id,
@@ -873,12 +937,12 @@ sub InsertData {
 
             # Global rights or Queue rights?
             if ( $item->{'CF'} ) {
 
             # Global rights or Queue rights?
             if ( $item->{'CF'} ) {
-                $object = RT::CustomField->new( $RT::SystemUser );
+                $object = RT::CustomField->new( RT->SystemUser );
                 my @columns = ( Name => $item->{'CF'} );
                 push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
                 $object->LoadByName( @columns );
             } elsif ( $item->{'Queue'} ) {
                 my @columns = ( Name => $item->{'CF'} );
                 push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
                 $object->LoadByName( @columns );
             } elsif ( $item->{'Queue'} ) {
-                $object = RT::Queue->new($RT::SystemUser);
+                $object = RT::Queue->new(RT->SystemUser);
                 $object->Load( $item->{'Queue'} );
             } else {
                 $object = $RT::System;
                 $object->Load( $item->{'Queue'} );
             } else {
                 $object = $RT::System;
@@ -888,7 +952,7 @@ sub InsertData {
 
             # Group rights or user rights?
             if ( $item->{'GroupDomain'} ) {
 
             # Group rights or user rights?
             if ( $item->{'GroupDomain'} ) {
-                $princ = RT::Group->new($RT::SystemUser);
+                $princ = RT::Group->new(RT->SystemUser);
                 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
                   $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
                 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
                 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
                   $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
                 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
@@ -903,9 +967,17 @@ sub InsertData {
                 } else {
                   $princ->Load( $item->{'GroupId'} );
                 }
                 } else {
                   $princ->Load( $item->{'GroupId'} );
                 }
+                unless ( $princ->Id ) {
+                    RT->Logger->error("Unable to load Group: GroupDomain => $item->{GroupDomain}, GroupId => $item->{GroupId}, Queue => $item->{Queue}");
+                    next;
+                }
             } else {
             } else {
-                $princ = RT::User->new($RT::SystemUser);
-                $princ->Load( $item->{'UserId'} );
+                $princ = RT::User->new(RT->SystemUser);
+                my ($ok, $msg) = $princ->Load( $item->{'UserId'} );
+                unless ( $ok ) {
+                    RT->Logger->error("Unable to load user: $item->{UserId} : $msg");
+                    next;
+                }
             }
 
             # Grant it
             }
 
             # Grant it
@@ -927,7 +999,7 @@ sub InsertData {
         $RT::Logger->debug("Creating ScripActions...");
 
         for my $item (@ScripActions) {
         $RT::Logger->debug("Creating ScripActions...");
 
         for my $item (@ScripActions) {
-            my $new_entry = RT::ScripAction->new($RT::SystemUser);
+            my $new_entry = RT::ScripAction->new(RT->SystemUser);
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
@@ -944,7 +1016,7 @@ sub InsertData {
         $RT::Logger->debug("Creating ScripConditions...");
 
         for my $item (@ScripConditions) {
         $RT::Logger->debug("Creating ScripConditions...");
 
         for my $item (@ScripConditions) {
-            my $new_entry = RT::ScripCondition->new($RT::SystemUser);
+            my $new_entry = RT::ScripCondition->new(RT->SystemUser);
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
@@ -961,7 +1033,7 @@ sub InsertData {
         $RT::Logger->debug("Creating templates...");
 
         for my $item (@Templates) {
         $RT::Logger->debug("Creating templates...");
 
         for my $item (@Templates) {
-            my $new_entry = new RT::Template($RT::SystemUser);
+            my $new_entry = RT::Template->new(RT->SystemUser);
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
             my ( $return, $msg ) = $new_entry->Create(%$item);
             unless ( $return ) {
                 $RT::Logger->error( $msg );
@@ -976,7 +1048,7 @@ sub InsertData {
         $RT::Logger->debug("Creating scrips...");
 
         for my $item (@Scrips) {
         $RT::Logger->debug("Creating scrips...");
 
         for my $item (@Scrips) {
-            my $new_entry = new RT::Scrip($RT::SystemUser);
+            my $new_entry = RT::Scrip->new(RT->SystemUser);
 
             my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
             push @queues, 0 unless @queues; # add global queue at least
 
             my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
             push @queues, 0 unless @queues; # add global queue at least
@@ -994,8 +1066,8 @@ sub InsertData {
         $RT::Logger->debug("done.");
     }
     if ( @Attributes ) {
         $RT::Logger->debug("done.");
     }
     if ( @Attributes ) {
-        $RT::Logger->debug("Creating predefined searches...");
-        my $sys = RT::System->new($RT::SystemUser);
+        $RT::Logger->debug("Creating attributes...");
+        my $sys = RT::System->new(RT->SystemUser);
 
         for my $item (@Attributes) {
             my $obj = delete $item->{Object}; # XXX: make this something loadable
 
         for my $item (@Attributes) {
             my $obj = delete $item->{Object}; # XXX: make this something loadable
@@ -1021,8 +1093,14 @@ sub InsertData {
         $RT::Logger->debug("done.");
     }
 
         $RT::Logger->debug("done.");
     }
 
-    my $db_type = RT->Config->Get('DatabaseType');
-    $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
+    # XXX: This disconnect doesn't really belong here; it's a relict from when
+    # this method was extracted from rt-setup-database.  However, too much
+    # depends on it to change without significant testing.  At the very least,
+    # we can provide a way to skip the side-effect.
+    if ( $args{disconnect_after} ) {
+        my $db_type = RT->Config->Get('DatabaseType');
+        $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
+    }
 
     $RT::Logger->debug("Done setting up database content.");
 
 
     $RT::Logger->debug("Done setting up database content.");
 
@@ -1041,10 +1119,10 @@ Given a userid, return that user's acl equivalence group
 sub ACLEquivGroupId {
     my $id = shift;
 
 sub ACLEquivGroupId {
     my $id = shift;
 
-    my $cu = $RT::SystemUser;
+    my $cu = RT->SystemUser;
     unless ( $cu ) {
         require RT::CurrentUser;
     unless ( $cu ) {
         require RT::CurrentUser;
-        $cu = new RT::CurrentUser;
+        $cu = RT::CurrentUser->new;
         $cu->LoadByName('RT_System');
         warn "Couldn't load RT_System user" unless $cu->id;
     }
         $cu->LoadByName('RT_System');
         warn "Couldn't load RT_System user" unless $cu->id;
     }
@@ -1054,11 +1132,110 @@ sub ACLEquivGroupId {
     return $equiv_group->Id;
 }
 
     return $equiv_group->Id;
 }
 
+=head2 QueryHistory
+
+Returns the SQL query history associated with this handle. The top level array
+represents a lists of request. Each request is a hash with metadata about the
+request (such as the URL) and a list of queries. You'll probably not be using this.
+
+=cut
+
+sub QueryHistory {
+    my $self = shift;
+
+    return $self->{QueryHistory};
+}
+
+=head2 AddRequestToHistory
+
+Adds a web request to the query history. It must be a hash with keys Path (a
+string) and Queries (an array reference of arrays, where elements are time,
+sql, bind parameters, and duration).
+
+=cut
+
+sub AddRequestToHistory {
+    my $self    = shift;
+    my $request = shift;
+
+    push @{ $self->{QueryHistory} }, $request;
+}
+
+=head2 Quote
+
+Returns the parameter quoted by DBI. B<You almost certainly do not need this.>
+Use bind parameters (C<?>) instead. This is used only outside the scope of interacting
+with the database.
+
+=cut
+
+sub Quote {
+    my $self = shift;
+    my $value = shift;
+
+    return $self->dbh->quote($value);
+}
+
+=head2 FillIn
+
+Takes a SQL query and an array reference of bind parameters and fills in the
+query's C<?> parameters.
+
+=cut
+
+sub FillIn {
+    my $self = shift;
+    my $sql  = shift;
+    my $bind = shift;
+
+    my $b = 0;
+
+    # is this regex sufficient?
+    $sql =~ s{\?}{$self->Quote($bind->[$b++])}eg;
+
+    return $sql;
+}
+
+# log a mason stack trace instead of a Carp::longmess because it's less painful
+# and uses mason component paths properly
+sub _LogSQLStatement {
+    my $self = shift;
+    my $statement = shift;
+    my $duration = shift;
+    my @bind = @_;
+
+    require HTML::Mason::Exceptions;
+    push @{$self->{'StatementLog'}} , ([Time::HiRes::time(), $statement, [@bind], $duration, HTML::Mason::Exception->new->as_string]);
+}
+
+
+sub _TableNames {
+    my $self = shift;
+    my $dbh = shift || $self->dbh;
+
+    {
+        local $@;
+        if (
+            $dbh->{Driver}->{Name} eq 'Pg'
+            && $dbh->{'pg_server_version'} >= 90200
+            && !eval { DBD::Pg->VERSION('2.19.3'); 1 }
+        ) {
+            die "You're using PostgreSQL 9.2 or newer. You have to upgrade DBD::Pg module to 2.19.3 or newer: $@";
+        }
+    }
+
+    my @res;
+
+    my $sth = $dbh->table_info( '', undef, undef, "'TABLE'");
+    while ( my $table = $sth->fetchrow_hashref ) {
+        push @res, $table->{TABLE_NAME} || $table->{table_name};
+    }
+
+    return @res;
+}
+
 __PACKAGE__->FinalizeDatabaseType;
 
 __PACKAGE__->FinalizeDatabaseType;
 
-eval "require RT::Handle_Vendor";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Handle_Vendor.pm});
-eval "require RT::Handle_Local";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Handle_Local.pm});
+RT::Base->_ImportOverlays();
 
 1;
 
 1;