1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2019 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
51 RT::Handle - RT's database handle
56 BEGIN { RT::LoadConfig() };
61 C<RT::Handle> is RT specific wrapper over one of L<DBIx::SearchBuilder::Handle>
62 classes. As RT works with different types of DBs we subclass repsective handler
63 from L<DBIx::SearchBuilder>. Type of the DB is defined by L<RT's DatabaseType
64 config option|RT_Config/DatabaseType>. You B<must> load this module only when
65 the configs have been loaded.
78 =head2 FinalizeDatabaseType
80 Sets RT::Handle's superclass to the correct subclass of
81 L<DBIx::SearchBuilder::Handle>, using the C<DatabaseType> configuration.
85 sub FinalizeDatabaseType {
86 my $db_type = RT->Config->Get('DatabaseType');
87 my $package = "DBIx::SearchBuilder::Handle::$db_type";
90 die "Unable to load DBIx::SearchBuilder database handle for '$db_type'.\n".
91 "Perhaps you've picked an invalid database type or spelled it incorrectly.\n".
94 @RT::Handle::ISA = ($package);
96 # We use COLLATE NOCASE to enforce case insensitivity on the normally
97 # case-sensitive SQLite, LOWER() approach works, but lucks performance
98 # due to absence of functional indexes
99 if ($db_type eq 'SQLite') {
100 no strict 'refs'; no warnings 'redefine';
101 *DBIx::SearchBuilder::Handle::SQLite::CaseSensitive = sub {0};
107 Connects to RT's database using credentials and options from the RT config.
116 my $db_type = RT->Config->Get('DatabaseType');
117 if ( $db_type eq 'Oracle' ) {
118 $ENV{'NLS_LANG'} = "AMERICAN_AMERICA.AL32UTF8";
119 $ENV{'NLS_NCHAR'} = "AL32UTF8";
122 $self->SUPER::Connect(
123 User => RT->Config->Get('DatabaseUser'),
124 Password => RT->Config->Get('DatabasePassword'),
125 DisconnectHandleOnDestroy => 1,
129 if ( $db_type eq 'mysql' ) {
130 my $version = $self->DatabaseVersion;
131 ($version) = $version =~ /^(\d+\.\d+)/;
132 $self->dbh->do("SET NAMES 'utf8'") if $version >= 4.1;
136 elsif ( $db_type eq 'Pg' ) {
137 my $version = $self->DatabaseVersion;
138 ($version) = $version =~ /^(\d+\.\d+)/;
139 $self->dbh->{pg_server_prepare} = 0 if $version > 9.1; #and we're using a deb-7 version DBD::Pg?
140 $self->dbh->do("SET bytea_output = 'escape'") if $version >= 9.0;
142 # Force UTF8, even when database encoding is not UTF8
143 # DBD::Pg used to do this for us prior to v3
144 $self->dbh->do('SET client_encoding TO UTF8;');
145 $self->dbh->{pg_enable_utf8} = -1;
150 $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');
155 Build the DSN for the RT database. Doesn't take any parameters, draws all that
163 # Unless the database port is a positive integer, we really don't want to pass it.
164 my $db_port = RT->Config->Get('DatabasePort');
165 $db_port = undef unless (defined $db_port && $db_port =~ /^(\d+)$/);
166 my $db_host = RT->Config->Get('DatabaseHost');
167 $db_host = undef unless $db_host;
168 my $db_name = RT->Config->Get('DatabaseName');
169 my $db_type = RT->Config->Get('DatabaseType');
170 $db_name = File::Spec->catfile($RT::VarPath, $db_name)
171 if $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name);
175 Database => $db_name,
179 if ( $db_type eq 'Oracle' && $db_host ) {
180 $args{'SID'} = delete $args{'Database'};
182 $self->SUPER::BuildDSN( %args );
184 if (RT->Config->Get('DatabaseExtraDSN')) {
185 my %extra = RT->Config->Get('DatabaseExtraDSN');
186 $self->{'dsn'} .= ";$_=$extra{$_}"
187 for sort keys %extra;
189 return $self->{'dsn'};
194 Returns the DSN for this handle. In order to get correct value you must
195 build DSN first, see L</BuildDSN>.
197 This is method can be called as class method, in this case creates
198 temporary handle object, L</BuildDSN builds DSN> and returns it.
204 return $self->SUPER::DSN if ref $self;
206 my $handle = $self->new;
213 Returns a DSN suitable for database creates and drops
214 and user creates and drops.
216 Gets RT's DSN first (see L<DSN>) and then change it according
217 to requirements of a database system RT's using.
224 my $db_name = RT->Config->Get('DatabaseName');
225 my $db_type = RT->Config->Get('DatabaseType');
227 my $dsn = $self->DSN;
228 if ( $db_type eq 'mysql' ) {
229 # with mysql, you want to connect sans database to funge things
230 $dsn =~ s/dbname=\Q$db_name//;
232 elsif ( $db_type eq 'Pg' ) {
233 # with postgres, you want to connect to template1 database
234 $dsn =~ s/dbname=\Q$db_name/dbname=template1/;
239 =head2 Database compatibility and integrity checks
248 unless ($RT::Handle and $RT::Handle->dbh) {
250 unless ( eval { RT::ConnectToDatabase(); 1 } ) {
251 return (0, 'no connection', "$@");
255 require RT::CurrentUser;
256 my $test_user = RT::CurrentUser->new;
257 $test_user->Load('RT_System');
258 unless ( $test_user->id ) {
259 return (0, 'no system user', "Couldn't find RT_System user in the DB '". $RT::Handle->DSN ."'");
262 $test_user = RT::CurrentUser->new;
263 $test_user->Load('Nobody');
264 unless ( $test_user->id ) {
265 return (0, 'no nobody user', "Couldn't find Nobody user in the DB '". $RT::Handle->DSN ."'");
271 sub CheckCompatibility {
274 my $state = shift || 'post';
276 my $db_type = RT->Config->Get('DatabaseType');
277 if ( $db_type eq "mysql" ) {
278 # Check which version we're running
279 my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
280 return (0, "couldn't get version of the mysql server")
283 ($version) = $version =~ /^(\d+\.\d+)/;
284 return (0, "RT is unsupported on MySQL versions before 4.1. Your version is $version.")
287 # MySQL must have InnoDB support
288 local $dbh->{FetchHashKeyName} = 'NAME_lc';
289 my $innodb = lc($dbh->selectall_hashref("SHOW ENGINES", "engine")->{InnoDB}{support} || "no");
290 if ( $innodb eq "no" ) {
291 return (0, "RT requires that MySQL be compiled with InnoDB table support.\n".
292 "See <http://dev.mysql.com/doc/mysql/en/innodb-storage-engine.html>\n".
293 "and check that there are no 'skip-innodb' lines in your my.cnf.");
294 } elsif ( $innodb eq "disabled" ) {
295 return (0, "RT requires that MySQL InnoDB table support be enabled.\n".
296 "Remove the 'skip-innodb' or 'innodb = OFF' line from your my.cnf file, restart MySQL, and try again.\n");
299 if ( $state eq 'post' ) {
300 my $show_table = sub { $dbh->selectrow_arrayref("SHOW CREATE TABLE $_[0]")->[1] };
301 unless ( $show_table->("Tickets") =~ /(?:ENGINE|TYPE)\s*=\s*InnoDB/i ) {
302 return (0, "RT requires that all its tables be of InnoDB type. Upgrade RT tables.");
305 unless ( $show_table->("Attachments") =~ /\bContent\b[^,]*BLOB/i ) {
306 return (0, "RT since version 3.8 has new schema for MySQL versions after 4.1.0\n"
307 ."Follow instructions in the UPGRADING.mysql file.");
311 if ($state =~ /^(create|post)$/) {
312 my $show_var = sub { $dbh->selectrow_arrayref("SHOW VARIABLES LIKE ?",{},$_[0])->[1] };
314 my $max_packet = $show_var->("max_allowed_packet");
315 if ($max_packet <= (5 * 1024 * 1024)) {
316 $max_packet = sprintf("%.1fM", $max_packet/1024/1024);
317 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";
320 my $full_version = $show_var->("version");
321 if ($full_version =~ /^5\.(\d+)\.(\d+)$/ and (($1 == 6 and $2 >= 20) or $1 > 6)) {
322 my $redo_log_size = $show_var->("innodb_log_file_size");
323 $redo_log_size *= $show_var->("innodb_log_files_in_group")
324 if $full_version =~ /^5\.(\d+)\.(\d+)$/ and (($1 == 6 and $2 >= 22) or $1 > 6);
326 if ($redo_log_size / 10 < 5 * 1024 * 1024) {
327 $redo_log_size = sprintf("%.1fM",$redo_log_size/1024/1024);
328 warn "innodb_log_file_size is set to $redo_log_size; attachments can only be 10% of this value on MySQL 5.6. Consider adjusting MySQL's innodb_log_file_size setting.\n";
339 my $dbh = $RT::Handle->dbh;
340 local $dbh->{'RaiseError'} = 0;
341 local $dbh->{'PrintError'} = 0;
342 my $has = ($dbh->selectrow_array("show variables like 'have_sphinx'"))[1];
343 $has ||= ($dbh->selectrow_array(
344 "select 'yes' from INFORMATION_SCHEMA.PLUGINS where PLUGIN_NAME = 'sphinx' AND PLUGIN_STATUS='active'"
347 return 0 unless lc($has||'') eq "yes";
351 =head2 Database maintanance
353 =head3 CreateDatabase $DBH
355 Creates a new database. This method can be used as class method.
357 Takes DBI handle. Many database systems require special handle to
358 allow you to create a new database, so you have to use L<SystemDSN>
359 method during connection.
361 Fetches type and name of the DB from the config.
367 my $dbh = shift or return (0, "No DBI handle provided");
368 my $db_type = RT->Config->Get('DatabaseType');
369 my $db_name = RT->Config->Get('DatabaseName');
372 if ( $db_type eq 'SQLite' ) {
373 return (1, 'Skipped as SQLite doesn\'t need any action');
375 elsif ( $db_type eq 'Oracle' ) {
376 my $db_user = RT->Config->Get('DatabaseUser');
377 my $db_pass = RT->Config->Get('DatabasePassword');
379 "CREATE USER $db_user IDENTIFIED BY $db_pass"
380 ." default tablespace USERS"
381 ." temporary tablespace TEMP"
382 ." quota unlimited on USERS"
385 return $status, "Couldn't create user $db_user identified by $db_pass."
386 ."\nError: ". $dbh->errstr;
388 $status = $dbh->do( "GRANT connect, resource TO $db_user" );
390 return $status, "Couldn't grant connect and resource to $db_user."
391 ."\nError: ". $dbh->errstr;
393 return (1, "Created user $db_user. All RT's objects should be in his schema.");
395 elsif ( $db_type eq 'Pg' ) {
396 $status = $dbh->do("CREATE DATABASE $db_name WITH ENCODING='UNICODE' TEMPLATE template0");
398 elsif ( $db_type eq 'mysql' ) {
399 $status = $dbh->do("CREATE DATABASE `$db_name` DEFAULT CHARACTER SET utf8");
402 $status = $dbh->do("CREATE DATABASE $db_name");
404 return ($status, $DBI::errstr);
407 =head3 DropDatabase $DBH
409 Drops RT's database. This method can be used as class method.
411 Takes DBI handle as first argument. Many database systems require
412 a special handle to allow you to drop a database, so you may have
413 to use L<SystemDSN> when acquiring the DBI handle.
415 Fetches the type and name of the database from the config.
421 my $dbh = shift or return (0, "No DBI handle provided");
423 my $db_type = RT->Config->Get('DatabaseType');
424 my $db_name = RT->Config->Get('DatabaseName');
426 if ( $db_type eq 'Oracle' ) {
427 my $db_user = RT->Config->Get('DatabaseUser');
428 my $status = $dbh->do( "DROP USER $db_user CASCADE" );
430 return 0, "Couldn't drop user $db_user."
431 ."\nError: ". $dbh->errstr;
433 return (1, "Successfully dropped user '$db_user' with his schema.");
435 elsif ( $db_type eq 'SQLite' ) {
437 $path = "$RT::VarPath/$path" unless substr($path, 0, 1) eq '/';
438 unlink $path or return (0, "Couldn't remove '$path': $!");
440 } elsif ( $db_type eq 'mysql' ) {
441 $dbh->do("DROP DATABASE `$db_name`")
442 or return (0, $DBI::errstr);
444 $dbh->do("DROP DATABASE ". $db_name)
445 or return (0, $DBI::errstr);
457 my $base_path = shift || $RT::EtcPath;
459 my $db_type = RT->Config->Get('DatabaseType');
460 return (1) if $db_type eq 'SQLite';
462 $dbh = $self->dbh if !$dbh && ref $self;
463 return (0, "No DBI handle provided") unless $dbh;
465 return (0, "'$base_path' doesn't exist") unless -e $base_path;
468 if ( -d $base_path ) {
469 $path = File::Spec->catfile( $base_path, "acl.$db_type");
470 $path = $self->GetVersionFile($dbh, $path);
472 $path = File::Spec->catfile( $base_path, "acl")
473 unless $path && -e $path;
474 return (0, "Couldn't find ACLs for $db_type")
481 do $path || return (0, "Couldn't load ACLs: " . $@);
483 foreach my $statement (@acl) {
484 my $sth = $dbh->prepare($statement)
485 or return (0, "Couldn't prepare SQL query:\n $statement\n\nERROR: ". $dbh->errstr);
486 unless ( $sth->execute ) {
487 return (0, "Couldn't run SQL query:\n $statement\n\nERROR: ". $sth->errstr);
500 my $base_path = (shift || $RT::EtcPath);
502 $dbh = $self->dbh if !$dbh && ref $self;
503 return (0, "No DBI handle provided") unless $dbh;
505 my $db_type = RT->Config->Get('DatabaseType');
508 if ( -d $base_path ) {
509 $file = $base_path . "/schema." . $db_type;
514 $file = $self->GetVersionFile( $dbh, $file );
516 return (0, "Couldn't find schema file(s) '$file*'");
518 unless ( -f $file && -r $file ) {
519 return (0, "File '$file' doesn't exist or couldn't be read");
524 open( my $fh_schema, '<', $file ) or die $!;
527 open( my $fh_schema_local, "<" . $self->GetVersionFile( $dbh, $RT::LocalEtcPath . "/schema." . $db_type ))
531 foreach my $line ( <$fh_schema>, ($_ = ';;'), $has_local? <$fh_schema_local>: () ) {
535 if ( $line =~ /;(\s*)$/ ) {
536 $statement =~ s/;(\s*)$//g;
537 push @schema, $statement;
541 close $fh_schema; close $fh_schema_local;
543 if ( $db_type eq 'Oracle' ) {
544 my $db_user = RT->Config->Get('DatabaseUser');
545 my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" );
547 return $status, "Couldn't set current schema to $db_user."
548 ."\nError: ". $dbh->errstr;
552 local $SIG{__WARN__} = sub {};
554 $dbh->begin_work or return (0, "Couldn't begin transaction: ". $dbh->errstr);
555 foreach my $statement (@schema) {
556 if ( $statement =~ /^\s*;$/ ) {
560 my $sth = $dbh->prepare($statement)
561 or return (0, "Couldn't prepare SQL query:\n$statement\n\nERROR: ". $dbh->errstr);
562 unless ( $sth->execute or $is_local ) {
563 return (0, "Couldn't run SQL query:\n$statement\n\nERROR: ". $sth->errstr);
566 $dbh->commit or return (0, "Couldn't commit transaction: ". $dbh->errstr);
573 my $base_path = shift || $RT::EtcPath;
575 my $db_type = RT->Config->Get('DatabaseType');
577 $dbh = $self->dbh if !$dbh && ref $self;
578 return (0, "No DBI handle provided") unless $dbh;
580 return (0, "'$base_path' doesn't exist") unless -e $base_path;
583 if ( -d $base_path ) {
584 $path = File::Spec->catfile( $base_path, "indexes");
585 return (0, "Couldn't find indexes file")
591 if ( $db_type eq 'Oracle' ) {
592 my $db_user = RT->Config->Get('DatabaseUser');
593 my $status = $dbh->do( "ALTER SESSION SET CURRENT_SCHEMA=$db_user" );
595 return $status, "Couldn't set current schema to $db_user."
596 ."\nError: ". $dbh->errstr;
601 eval { require $path; 1 }
602 or return (0, "Couldn't execute '$path': " . $@);
606 =head1 GetVersionFile
608 Takes base name of the file as argument, scans for <base name>-<version> named
609 files and returns file name with closest version to the version of the RT DB.
616 my $base_name = shift;
618 my $db_version = ref $self
619 ? $self->DatabaseVersion
621 my $tmp = RT::Handle->new;
623 $tmp->DatabaseVersion;
627 my @files = File::Glob::bsd_glob("$base_name*");
628 return '' unless @files;
630 my %version = map { $_ =~ /\.\w+-([-\w\.]+)$/; ($1||0) => $_ } @files;
632 foreach ( reverse sort cmp_version keys %version ) {
633 if ( cmp_version( $db_version, $_ ) >= 0 ) {
639 return defined $version? $version{ $version } : undef;
651 sub cmp_version($$) {
653 my @a = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
654 split /([^0-9]+)/, $a;
655 my @b = grep defined, map { /^[0-9]+$/? $_ : /^[a-zA-Z]+$/? $word{$_}|| -10 : undef }
656 split /([^0-9]+)/, $b;
658 ? push @b, (0) x (@a-@b)
659 : push @a, (0) x (@b-@a);
660 for ( my $i = 0; $i < @a; $i++ ) {
661 return $a[$i] <=> $b[$i] if $a[$i] <=> $b[$i];
673 =head2 InsertInitialData
675 Inserts system objects into RT's DB, like system user or 'nobody',
676 internal groups and other records required. However, this method
677 doesn't insert any real users like 'root' and you have to use
678 InsertData or another way to do that.
680 Takes no arguments. Returns status and message tuple.
682 It's safe to call this method even if those objects already exist.
686 sub InsertInitialData {
691 # create RT_System user and grant him rights
693 require RT::CurrentUser;
695 my $test_user = RT::User->new( RT::CurrentUser->new() );
696 $test_user->Load('RT_System');
697 if ( $test_user->id ) {
698 push @warns, "Found system user in the DB.";
701 my $user = RT::User->new( RT::CurrentUser->new() );
702 my ( $val, $msg ) = $user->_BootstrapCreate(
704 RealName => 'The RT System itself',
705 Comments => 'Do not delete or modify this user. '
706 . 'It is integral to RT\'s internal database structures',
708 LastUpdatedBy => '1',
710 return ($val, $msg) unless $val;
712 DBIx::SearchBuilder::Record::Cachable->FlushCache;
715 # init RT::SystemUser and RT::System objects
716 RT::InitSystemObjects();
717 unless ( RT->SystemUser->id ) {
718 return (0, "Couldn't load system user");
721 # grant SuperUser right to system user
723 my $test_ace = RT::ACE->new( RT->SystemUser );
724 $test_ace->LoadByCols(
725 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
726 PrincipalType => 'Group',
727 RightName => 'SuperUser',
728 ObjectType => 'RT::System',
731 if ( $test_ace->id ) {
732 push @warns, "System user has global SuperUser right.";
734 my $ace = RT::ACE->new( RT->SystemUser );
735 my ( $val, $msg ) = $ace->_BootstrapCreate(
736 PrincipalId => ACLEquivGroupId( RT->SystemUser->Id ),
737 PrincipalType => 'Group',
738 RightName => 'SuperUser',
739 ObjectType => 'RT::System',
742 return ($val, $msg) unless $val;
744 DBIx::SearchBuilder::Record::Cachable->FlushCache;
748 # $self->loc('Everyone'); # For the string extractor to get a string to localize
749 # $self->loc('Privileged'); # For the string extractor to get a string to localize
750 # $self->loc('Unprivileged'); # For the string extractor to get a string to localize
751 foreach my $name (qw(Everyone Privileged Unprivileged)) {
752 my $group = RT::Group->new( RT->SystemUser );
753 $group->LoadSystemInternalGroup( $name );
755 push @warns, "System group '$name' already exists.";
759 $group = RT::Group->new( RT->SystemUser );
760 my ( $val, $msg ) = $group->_Create(
761 Domain => 'SystemInternal',
762 Description => 'Pseudogroup for internal use', # loc
766 return ($val, $msg) unless $val;
771 my $user = RT::User->new( RT->SystemUser );
772 $user->Load('Nobody');
774 push @warns, "Found 'Nobody' user in the DB.";
777 my ( $val, $msg ) = $user->Create(
779 RealName => 'Nobody in particular',
780 Comments => 'Do not delete or modify this user. It is integral '
781 .'to RT\'s internal data structures',
784 return ($val, $msg) unless $val;
787 if ( $user->HasRight( Right => 'OwnTicket', Object => $RT::System ) ) {
788 push @warns, "User 'Nobody' has global OwnTicket right.";
790 my ( $val, $msg ) = $user->PrincipalObj->GrantRight(
791 Right => 'OwnTicket',
792 Object => $RT::System,
794 return ($val, $msg) unless $val;
798 # rerun to get init Nobody as well
799 RT::InitSystemObjects();
802 foreach my $name (qw(Owner Requestor Cc AdminCc)) {
803 my $group = RT->System->RoleGroup( $name );
805 push @warns, "System role '$name' already exists.";
809 $group = RT::Group->new( RT->SystemUser );
810 my ( $val, $msg ) = $group->CreateRoleGroup(
812 Object => RT->System,
813 Description => 'SystemRolegroup for internal use', # loc
814 InsideTransaction => 0,
816 return ($val, $msg) unless $val;
819 push @warns, "You appear to have a functional RT database."
822 return (1, join "\n", @warns);
827 Load some sort of data into the database, takes path to a file.
833 my $datafile = shift;
834 my $root_password = shift;
836 disconnect_after => 1,
840 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
841 our (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
842 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
843 local (@Groups, @Users, @Members, @ACL, @Queues, @ScripActions, @ScripConditions,
844 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final);
847 $RT::Logger->debug("Going to load '$datafile' data file");
848 eval { require $datafile }
849 or return (0, "Couldn't load data from '$datafile' for import:\n\nERROR:". $@);
852 $RT::Logger->debug("Running initial actions...");
853 foreach ( @Initial ) {
855 eval { $_->(); 1 } or return (0, "One of initial functions failed: $@");
857 $RT::Logger->debug("Done.");
860 $RT::Logger->debug("Creating groups...");
861 foreach my $item (@Groups) {
862 my $attributes = delete $item->{ Attributes };
863 my $new_entry = RT::Group->new( RT->SystemUser );
864 $item->{'Domain'} ||= 'UserDefined';
865 my $member_of = delete $item->{'MemberOf'};
866 my $members = delete $item->{'Members'};
867 my ( $return, $msg ) = $new_entry->_Create(%$item);
869 $RT::Logger->error( $msg );
872 $RT::Logger->debug($return .".");
873 $_->{Object} = $new_entry for @{$attributes || []};
874 push @Attributes, @{$attributes || []};
877 $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
878 foreach( @$member_of ) {
879 my $parent = RT::Group->new(RT->SystemUser);
880 if ( ref $_ eq 'HASH' ) {
881 $parent->LoadByCols( %$_ );
884 $parent->LoadUserDefinedGroup( $_ );
888 "(Error: wrong format of MemberOf field."
889 ." Should be name of user defined group or"
890 ." hash reference with 'column => value' pairs."
891 ." Use array reference to add to multiple groups)"
895 unless ( $parent->Id ) {
896 $RT::Logger->error("(Error: couldn't load group to add member)");
899 my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
901 $RT::Logger->error( $msg );
903 $RT::Logger->debug( $return ."." );
907 push @Members, map { +{Group => $new_entry->id,
908 Class => "RT::User", Name => $_} }
909 @{ $members->{Users} || [] };
910 push @Members, map { +{Group => $new_entry->id,
911 Class => "RT::Group", Name => $_} }
912 @{ $members->{Groups} || [] };
914 $RT::Logger->debug("done.");
917 $RT::Logger->debug("Creating users...");
918 foreach my $item (@Users) {
919 my $member_of = delete $item->{'MemberOf'};
920 if ( $item->{'Name'} eq 'root' && $root_password ) {
921 $item->{'Password'} = $root_password;
923 my $attributes = delete $item->{ Attributes };
924 my $new_entry = RT::User->new( RT->SystemUser );
925 my ( $return, $msg ) = $new_entry->Create(%$item);
927 $RT::Logger->error( $msg );
929 $RT::Logger->debug( $return ."." );
930 $_->{Object} = $new_entry for @{$attributes || []};
931 push @Attributes, @{$attributes || []};
934 $member_of = [ $member_of ] unless ref $member_of eq 'ARRAY';
935 foreach( @$member_of ) {
936 my $parent = RT::Group->new($RT::SystemUser);
937 if ( ref $_ eq 'HASH' ) {
938 $parent->LoadByCols( %$_ );
941 $parent->LoadUserDefinedGroup( $_ );
945 "(Error: wrong format of MemberOf field."
946 ." Should be name of user defined group or"
947 ." hash reference with 'column => value' pairs."
948 ." Use array reference to add to multiple groups)"
952 unless ( $parent->Id ) {
953 $RT::Logger->error("(Error: couldn't load group to add member)");
956 my ( $return, $msg ) = $parent->AddMember( $new_entry->Id );
958 $RT::Logger->error( $msg );
960 $RT::Logger->debug( $return ."." );
965 $RT::Logger->debug("done.");
968 $RT::Logger->debug("Adding users and groups to groups...");
969 for my $item (@Members) {
970 my $group = RT::Group->new(RT->SystemUser);
971 $group->LoadUserDefinedGroup( delete $item->{Group} );
972 unless ($group->Id) {
973 RT->Logger->error("Unable to find group '$group' to add members to");
977 my $class = delete $item->{Class} || 'RT::User';
978 my $member = $class->new( RT->SystemUser );
979 $item->{Domain} = 'UserDefined' if $member->isa("RT::Group");
980 $member->LoadByCols( %$item );
981 unless ($member->Id) {
982 RT->Logger->error("Unable to find $class '".($item->{id} || $item->{Name})."' to add to ".$group->Name);
986 my ( $return, $msg) = $group->AddMember( $member->PrincipalObj->Id );
988 $RT::Logger->error( $msg );
990 $RT::Logger->debug( $return ."." );
995 $RT::Logger->debug("Creating queues...");
996 for my $item (@Queues) {
997 my $attributes = delete $item->{ Attributes };
998 my $new_entry = RT::Queue->new(RT->SystemUser);
999 my ( $return, $msg ) = $new_entry->Create(%$item);
1000 unless ( $return ) {
1001 $RT::Logger->error( $msg );
1003 $RT::Logger->debug( $return ."." );
1004 $_->{Object} = $new_entry for @{$attributes || []};
1005 push @Attributes, @{$attributes || []};
1008 $RT::Logger->debug("done.");
1010 if ( @CustomFields ) {
1011 $RT::Logger->debug("Creating custom fields...");
1012 for my $item ( @CustomFields ) {
1013 my $attributes = delete $item->{ Attributes };
1014 my $new_entry = RT::CustomField->new( RT->SystemUser );
1015 my $values = delete $item->{'Values'};
1017 # Back-compat for the old "Queue" argument
1018 if ( exists $item->{'Queue'} ) {
1019 $item->{'LookupType'} ||= 'RT::Queue-RT::Ticket';
1020 $RT::Logger->warn("Queue provided for non-ticket custom field")
1021 unless $item->{'LookupType'} =~ /^RT::Queue-/;
1022 $item->{'ApplyTo'} = delete $item->{'Queue'};
1025 my $apply_to = delete $item->{'ApplyTo'};
1027 if ( $item->{'BasedOn'} ) {
1028 if ( $item->{'BasedOn'} =~ /^\d+$/) {
1029 # Already have an ID -- should be fine
1030 } elsif ( $item->{'LookupType'} ) {
1031 my $basedon = RT::CustomField->new($RT::SystemUser);
1032 my ($ok, $msg ) = $basedon->LoadByCols(
1033 Name => $item->{'BasedOn'},
1034 LookupType => $item->{'LookupType'},
1037 $item->{'BasedOn'} = $basedon->Id;
1039 $RT::Logger->error("Unable to load $item->{BasedOn} as a $item->{LookupType} CF. Skipping BasedOn: $msg");
1040 delete $item->{'BasedOn'};
1043 $RT::Logger->error("Unable to load CF $item->{BasedOn} because no LookupType was specified. Skipping BasedOn");
1044 delete $item->{'BasedOn'};
1049 my ( $return, $msg ) = $new_entry->Create(%$item);
1051 $RT::Logger->error( $msg );
1055 foreach my $value ( @{$values} ) {
1056 ( $return, $msg ) = $new_entry->AddValue(%$value);
1057 $RT::Logger->error( $msg ) unless $return;
1060 my $class = $new_entry->RecordClassFromLookupType;
1062 if ($new_entry->IsOnlyGlobal and $apply_to) {
1063 $RT::Logger->warn("ApplyTo provided for global custom field ".$new_entry->Name );
1067 # Apply to all by default
1068 my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
1069 ( $return, $msg) = $ocf->Create( CustomField => $new_entry->Id );
1070 $RT::Logger->error( $msg ) unless $return and $ocf->Id;
1072 $apply_to = [ $apply_to ] unless ref $apply_to;
1073 for my $name ( @{ $apply_to } ) {
1074 my $obj = $class->new(RT->SystemUser);
1077 my $ocf = RT::ObjectCustomField->new(RT->SystemUser);
1078 ( $return, $msg ) = $ocf->Create(
1079 CustomField => $new_entry->Id,
1080 ObjectId => $obj->Id,
1082 $RT::Logger->error( $msg ) unless $return and $ocf->Id;
1084 $RT::Logger->error("Could not find $class $name to apply ".$new_entry->Name." to" );
1090 $_->{Object} = $new_entry for @{$attributes || []};
1091 push @Attributes, @{$attributes || []};
1094 $RT::Logger->debug("done.");
1097 $RT::Logger->debug("Creating ACL...");
1098 for my $item (@ACL) {
1100 my ($princ, $object);
1102 # Global rights or Queue rights?
1103 if ( $item->{'CF'} ) {
1104 $object = RT::CustomField->new( RT->SystemUser );
1105 my @columns = ( Name => $item->{'CF'} );
1106 push @columns, LookupType => $item->{'LookupType'} if $item->{'LookupType'};
1107 push @columns, ObjectId => $item->{'ObjectId'} if $item->{'ObjectId'};
1108 push @columns, Queue => $item->{'Queue'} if $item->{'Queue'} and not ref $item->{'Queue'};
1109 my ($ok, $msg) = $object->LoadByName( @columns );
1111 RT->Logger->error("Unable to load CF ".$item->{CF}.": $msg");
1114 } elsif ( $item->{'Queue'} ) {
1115 $object = RT::Queue->new(RT->SystemUser);
1116 my ($ok, $msg) = $object->Load( $item->{'Queue'} );
1118 RT->Logger->error("Unable to load queue ".$item->{Queue}.": $msg");
1121 } elsif ( $item->{ObjectType} and $item->{ObjectId}) {
1122 $object = $item->{ObjectType}->new(RT->SystemUser);
1123 my ($ok, $msg) = $object->Load( $item->{ObjectId} );
1125 RT->Logger->error("Unable to load ".$item->{ObjectType}." ".$item->{ObjectId}.": $msg");
1129 $object = $RT::System;
1132 # Group rights or user rights?
1133 if ( $item->{'GroupDomain'} ) {
1134 $princ = RT::Group->new(RT->SystemUser);
1135 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
1136 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
1137 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
1138 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
1139 } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
1140 $princ->LoadRoleGroup( Object => RT->System, Name => $item->{'GroupType'} );
1141 } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
1144 $princ->LoadRoleGroup( Object => $object, Name => $item->{'GroupType'} );
1146 $princ->Load( $item->{'GroupId'} );
1148 unless ( $princ->Id ) {
1149 RT->Logger->error("Unable to load Group: GroupDomain => $item->{GroupDomain}, GroupId => $item->{GroupId}, Queue => $item->{Queue}");
1153 $princ = RT::User->new(RT->SystemUser);
1154 my ($ok, $msg) = $princ->Load( $item->{'UserId'} );
1156 RT->Logger->error("Unable to load user: $item->{UserId} : $msg");
1162 my @rights = ref($item->{'Right'}) eq 'ARRAY' ? @{$item->{'Right'}} : $item->{'Right'};
1163 foreach my $right ( @rights ) {
1164 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
1168 unless ( $return ) {
1169 $RT::Logger->error( $msg );
1172 $RT::Logger->debug( $return ."." );
1176 $RT::Logger->debug("done.");
1179 if ( @ScripActions ) {
1180 $RT::Logger->debug("Creating ScripActions...");
1182 for my $item (@ScripActions) {
1183 my $new_entry = RT::ScripAction->new(RT->SystemUser);
1184 my ( $return, $msg ) = $new_entry->Create(%$item);
1185 unless ( $return ) {
1186 $RT::Logger->error( $msg );
1189 $RT::Logger->debug( $return ."." );
1193 $RT::Logger->debug("done.");
1196 if ( @ScripConditions ) {
1197 $RT::Logger->debug("Creating ScripConditions...");
1199 for my $item (@ScripConditions) {
1200 my $new_entry = RT::ScripCondition->new(RT->SystemUser);
1201 my ( $return, $msg ) = $new_entry->Create(%$item);
1202 unless ( $return ) {
1203 $RT::Logger->error( $msg );
1206 $RT::Logger->debug( $return ."." );
1210 $RT::Logger->debug("done.");
1214 $RT::Logger->debug("Creating templates...");
1216 for my $item (@Templates) {
1217 my $new_entry = RT::Template->new(RT->SystemUser);
1218 my ( $return, $msg ) = $new_entry->Create(%$item);
1219 unless ( $return ) {
1220 $RT::Logger->error( $msg );
1223 $RT::Logger->debug( $return ."." );
1226 $RT::Logger->debug("done.");
1229 $RT::Logger->debug("Creating scrips...");
1231 for my $item (@Scrips) {
1232 my $new_entry = RT::Scrip->new(RT->SystemUser);
1234 my @queues = ref $item->{'Queue'} eq 'ARRAY'? @{ $item->{'Queue'} }: $item->{'Queue'} || 0;
1235 push @queues, 0 unless @queues; # add global queue at least
1237 my ( $return, $msg ) = $new_entry->Create( %$item, Queue => shift @queues );
1238 unless ( $return ) {
1239 $RT::Logger->error( $msg );
1243 $RT::Logger->debug( $return ."." );
1245 foreach my $q ( @queues ) {
1246 my ($return, $msg) = $new_entry->AddToObject(
1248 Stage => $item->{'Stage'},
1250 $RT::Logger->error( "Couldn't apply scrip to $q: $msg" )
1254 $RT::Logger->debug("done.");
1256 if ( @Attributes ) {
1257 $RT::Logger->debug("Creating attributes...");
1258 my $sys = RT::System->new(RT->SystemUser);
1260 for my $item (@Attributes) {
1261 my $obj = delete $item->{Object};
1263 if ( ref $obj eq 'CODE' ) {
1268 my ( $return, $msg ) = $obj->AddAttribute (%$item);
1269 unless ( $return ) {
1270 $RT::Logger->error( $msg );
1273 $RT::Logger->debug( $return ."." );
1276 $RT::Logger->debug("done.");
1279 $RT::Logger->debug("Running final actions...");
1283 $RT::Logger->error( "Failed to run one of final actions: $@" )
1286 $RT::Logger->debug("done.");
1289 # XXX: This disconnect doesn't really belong here; it's a relict from when
1290 # this method was extracted from rt-setup-database. However, too much
1291 # depends on it to change without significant testing. At the very least,
1292 # we can provide a way to skip the side-effect.
1293 if ( $args{disconnect_after} ) {
1294 my $db_type = RT->Config->Get('DatabaseType');
1295 $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
1298 $RT::Logger->debug("Done setting up database content.");
1300 # TODO is it ok to return 1 here? If so, the previous codes in this sub
1301 # should return (0, $msg) if error happens instead of just warning.
1302 # anyway, we need to return something here to tell if everything is ok
1303 return( 1, 'Done inserting data' );
1306 =head2 ACLEquivGroupId
1308 Given a userid, return that user's acl equivalence group
1312 sub ACLEquivGroupId {
1315 my $cu = RT->SystemUser;
1317 require RT::CurrentUser;
1318 $cu = RT::CurrentUser->new;
1319 $cu->LoadByName('RT_System');
1320 warn "Couldn't load RT_System user" unless $cu->id;
1323 my $equiv_group = RT::Group->new( $cu );
1324 $equiv_group->LoadACLEquivalenceGroup( $id );
1325 return $equiv_group->Id;
1330 Returns the SQL query history associated with this handle. The top level array
1331 represents a lists of request. Each request is a hash with metadata about the
1332 request (such as the URL) and a list of queries. You'll probably not be using this.
1339 return $self->{QueryHistory};
1342 =head2 AddRequestToHistory
1344 Adds a web request to the query history. It must be a hash with keys Path (a
1345 string) and Queries (an array reference of arrays, where elements are time,
1346 sql, bind parameters, and duration).
1350 sub AddRequestToHistory {
1352 my $request = shift;
1354 push @{ $self->{QueryHistory} }, $request;
1359 Returns the parameter quoted by DBI. B<You almost certainly do not need this.>
1360 Use bind parameters (C<?>) instead. This is used only outside the scope of interacting
1369 return $self->dbh->quote($value);
1374 Takes a SQL query and an array reference of bind parameters and fills in the
1375 query's C<?> parameters.
1386 # is this regex sufficient?
1387 $sql =~ s{\?}{$self->Quote($bind->[$b++])}eg;
1397 my $db_type = RT->Config->Get('DatabaseType');
1398 my $dbh = $self->dbh;
1401 if ( $db_type eq 'mysql' ) {
1402 $list = $dbh->selectall_arrayref(
1403 'select distinct table_name, index_name from information_schema.statistics where table_schema = ?',
1404 undef, scalar RT->Config->Get('DatabaseName')
1407 elsif ( $db_type eq 'Pg' ) {
1408 $list = $dbh->selectall_arrayref(
1409 'select tablename, indexname from pg_indexes',
1413 elsif ( $db_type eq 'SQLite' ) {
1414 $list = $dbh->selectall_arrayref(
1415 'select tbl_name, name from sqlite_master where type = ?',
1419 elsif ( $db_type eq 'Oracle' ) {
1420 $list = $dbh->selectall_arrayref(
1421 'select table_name, index_name from all_indexes where index_name NOT LIKE ? AND lower(Owner) = ?',
1422 undef, 'SYS_%$$', lc RT->Config->Get('DatabaseUser'),
1426 die "Not implemented";
1428 push @{ $res{ lc $_->[0] } ||= [] }, lc $_->[1] foreach @$list;
1432 sub IndexesThatBeginWith {
1434 my %args = (Table => undef, Columns => [], @_);
1436 my %indexes = $self->Indexes;
1438 my @check = @{ $args{'Columns'} };
1441 foreach my $index ( @{ $indexes{ lc $args{'Table'} } || [] } ) {
1442 my %info = $self->IndexInfo( Table => $args{'Table'}, Name => $index );
1443 next if @{ $info{'Columns'} } < @check;
1444 my $check = join ',', @check;
1445 next if join( ',', @{ $info{'Columns'} } ) !~ /^\Q$check\E(?:,|$)/i;
1449 return sort { @{ $a->{'Columns'} } <=> @{ $b->{'Columns'} } } @list;
1454 my %args = (Table => undef, Name => undef, @_);
1456 my $db_type = RT->Config->Get('DatabaseType');
1457 my $dbh = $self->dbh;
1460 Table => lc $args{'Table'},
1461 Name => lc $args{'Name'},
1463 if ( $db_type eq 'mysql' ) {
1464 my $list = $dbh->selectall_arrayref(
1465 'select NON_UNIQUE, COLUMN_NAME, SUB_PART
1466 from information_schema.statistics
1467 where table_schema = ? AND LOWER(table_name) = ? AND index_name = ?
1468 ORDER BY SEQ_IN_INDEX',
1469 undef, scalar RT->Config->Get('DatabaseName'), lc $args{'Table'}, $args{'Name'},
1471 return () unless $list && @$list;
1472 $res{'Unique'} = $list->[0][0]? 0 : 1;
1473 $res{'Functional'} = 0;
1474 $res{'Columns'} = [ map $_->[1], @$list ];
1476 elsif ( $db_type eq 'Pg' ) {
1477 my $index = $dbh->selectrow_hashref(
1478 'select ix.*, pg_get_expr(ix.indexprs, ix.indrelid) as functions
1480 pg_class t, pg_class i, pg_index ix
1484 and i.relname ilike ?
1485 and ix.indrelid = t.oid
1486 and ix.indexrelid = i.oid
1488 undef, $args{'Table'}, 'r', $args{'Name'},
1490 return () unless $index && keys %$index;
1491 $res{'Unique'} = $index->{'indisunique'};
1492 $res{'Functional'} = (grep $_ == 0, split ' ', $index->{'indkey'})? 1 : 0;
1493 $res{'Columns'} = [ map int($_), split ' ', $index->{'indkey'} ];
1494 my $columns = $dbh->selectall_hashref(
1495 'select a.attnum, a.attname
1496 from pg_attribute a where a.attrelid = ?',
1497 'attnum', undef, $index->{'indrelid'}
1499 if ($index->{'functions'}) {
1500 # XXX: this is good enough for us
1501 $index->{'functions'} = [ split /,\s+/, $index->{'functions'} ];
1503 foreach my $e ( @{ $res{'Columns'} } ) {
1504 if (exists $columns->{$e} ) {
1505 $e = $columns->{$e}{'attname'};
1508 $e = shift @{ $index->{'functions'} };
1512 foreach my $column ( @{$res{'Columns'}} ) {
1513 next unless $column =~ s/^lower\( \s* \(? (\w+) \)? (?:::text)? \s* \)$/$1/ix;
1514 $res{'CaseInsensitive'}{ lc $1 } = 1;
1517 elsif ( $db_type eq 'SQLite' ) {
1518 my $list = $dbh->selectall_arrayref("pragma index_info('$args{'Name'}')");
1519 return () unless $list && @$list;
1521 $res{'Functional'} = 0;
1522 $res{'Columns'} = [ map $_->[2], @$list ];
1524 $list = $dbh->selectall_arrayref("pragma index_list('$args{'Table'}')");
1525 $res{'Unique'} = (grep lc $_->[1] eq lc $args{'Name'}, @$list)[0][2]? 1 : 0;
1527 elsif ( $db_type eq 'Oracle' ) {
1528 my $index = $dbh->selectrow_arrayref(
1529 'select uniqueness, funcidx_status from all_indexes
1530 where lower(table_name) = ? AND lower(index_name) = ? AND LOWER(Owner) = ?',
1531 undef, lc $args{'Table'}, lc $args{'Name'}, lc RT->Config->Get('DatabaseUser'),
1533 return () unless $index && @$index;
1534 $res{'Unique'} = $index->[0] eq 'UNIQUE'? 1 : 0;
1535 $res{'Functional'} = $index->[1] ? 1 : 0;
1537 my %columns = map @$_, @{ $dbh->selectall_arrayref(
1538 'select column_position, column_name from all_ind_columns
1539 where lower(table_name) = ? AND lower(index_name) = ? AND LOWER(index_owner) = ?',
1540 undef, lc $args{'Table'}, lc $args{'Name'}, lc RT->Config->Get('DatabaseUser'),
1542 $columns{ $_->[0] } = $_->[1] foreach @{ $dbh->selectall_arrayref(
1543 'select column_position, column_expression from all_ind_expressions
1544 where lower(table_name) = ? AND lower(index_name) = ? AND LOWER(index_owner) = ?',
1545 undef, lc $args{'Table'}, lc $args{'Name'}, lc RT->Config->Get('DatabaseUser'),
1547 $res{'Columns'} = [ map $columns{$_}, sort { $a <=> $b } keys %columns ];
1549 foreach my $column ( @{$res{'Columns'}} ) {
1550 next unless $column =~ s/^lower\( \s* " (\w+) " \s* \)$/$1/ix;
1551 $res{'CaseInsensitive'}{ lc $1 } = 1;
1555 die "Not implemented";
1557 $_ = lc $_ foreach @{ $res{'Columns'} };
1563 my %args = (Table => undef, Name => undef, @_);
1565 my $db_type = RT->Config->Get('DatabaseType');
1566 my $dbh = $self->dbh;
1567 local $dbh->{'PrintError'} = 0;
1568 local $dbh->{'RaiseError'} = 0;
1571 if ( $db_type eq 'mysql' ) {
1572 $args{'Table'} = $self->_CanonicTableNameMysql( $args{'Table'} );
1574 'drop index '. $dbh->quote_identifier($args{'Name'}) ." on $args{'Table'}",
1577 elsif ( $db_type eq 'Pg' ) {
1578 $res = $dbh->do("drop index $args{'Name'} CASCADE");
1580 elsif ( $db_type eq 'SQLite' ) {
1581 $res = $dbh->do("drop index $args{'Name'}");
1583 elsif ( $db_type eq 'Oracle' ) {
1584 my $user = RT->Config->Get('DatabaseUser');
1585 # Check if it has constraints associated with it
1586 my ($constraint) = $dbh->selectrow_arrayref(
1587 'SELECT constraint_name, table_name FROM all_constraints WHERE LOWER(owner) = ? AND LOWER(index_name) = ?',
1588 undef, lc $user, lc $args{'Name'}
1591 my ($constraint_name, $table) = @{$constraint};
1592 $res = $dbh->do("ALTER TABLE $user.$table DROP CONSTRAINT $constraint_name");
1594 $res = $dbh->do("DROP INDEX $user.$args{'Name'}");
1598 die "Not implemented";
1600 my $desc = $self->IndexDescription( %args );
1601 return ($res, $res? "Dropped $desc" : "Couldn't drop $desc: ". $dbh->errstr);
1604 sub _CanonicTableNameMysql {
1607 return $table unless $table;
1608 # table name can be case sensitivity in DDL
1609 # use LOWER to workaround mysql "bug"
1610 return ($self->dbh->selectrow_array(
1612 FROM information_schema.tables
1613 WHERE table_schema = ? AND LOWER(table_name) = ?',
1614 undef, scalar RT->Config->Get('DatabaseName'), lc $table
1618 sub DropIndexIfExists {
1620 my %args = (Table => undef, Name => undef, @_);
1622 my %indexes = $self->Indexes;
1623 return (1, ucfirst($self->IndexDescription( %args )) ." doesn't exists")
1624 unless grep $_ eq lc $args{'Name'},
1625 @{ $indexes{ lc $args{'Table'} } || []};
1626 return $self->DropIndex(%args);
1631 my %args = ( Table => undef, Name => undef, Columns => [], CaseInsensitive => {}, @_ );
1633 $args{'Table'} = $self->_CanonicTableNameMysql( $args{'Table'} )
1634 if RT->Config->Get('DatabaseType') eq 'mysql';
1636 my $name = $args{'Name'};
1638 my %indexes = $self->Indexes;
1639 %indexes = map { $_ => 1 } @{ $indexes{ lc $args{'Table'} } || [] };
1641 $i++ while $indexes{ lc($args{'Table'}).$i };
1642 $name = lc($args{'Table'}).$i;
1645 my @columns = @{ $args{'Columns'} };
1646 if ( $self->CaseSensitive ) {
1647 foreach my $column ( @columns ) {
1648 next unless $args{'CaseInsensitive'}{ lc $column };
1649 $column = "LOWER($column)";
1654 . ($args{'Unique'}? ' UNIQUE' : '')
1655 ." INDEX $name ON $args{'Table'}"
1656 ."(". join( ', ', @columns ) .")"
1659 my $res = $self->dbh->do( $sql );
1662 undef, "Failed to create ". $self->IndexDescription( %args )
1663 ." (sql: $sql): ". $self->dbh->errstr
1666 return ($name, "Created ". $self->IndexDescription( %args ) );
1669 sub IndexDescription {
1674 ($args{'Unique'}? 'unique ' : '')
1676 . ($args{'Name'}? " $args{'Name'}" : '')
1677 . ( @{$args{'Columns'}||[]}?
1679 . join(', ', @{$args{'Columns'}})
1680 . (@{$args{'Optional'}||[]}? '['. join(', ', '', @{$args{'Optional'}}).']' : '' )
1684 . ($args{'Table'}? " on $args{'Table'}" : '')
1689 sub MakeSureIndexExists {
1691 my %args = ( Table => undef, Columns => [], Optional => [], @_ );
1693 my @list = $self->IndexesThatBeginWith(
1694 Table => $args{'Table'}, Columns => [@{$args{'Columns'}}, @{$args{'Optional'}}],
1697 RT->Logger->debug( ucfirst $self->IndexDescription(
1698 Table => $args{'Table'}, Columns => [@{$args{'Columns'}}, @{$args{'Optional'}}],
1703 @list = $self->IndexesThatBeginWith(
1704 Table => $args{'Table'}, Columns => $args{'Columns'},
1707 my ($status, $msg) = $self->CreateIndex(
1708 Table => $args{'Table'}, Columns => [@{$args{'Columns'}}, @{$args{'Optional'}}],
1710 my $method = $status ? 'debug' : 'warning';
1711 RT->Logger->$method($msg);
1715 ucfirst $self->IndexDescription(
1718 .' exists, you may consider replacing it with '
1719 . $self->IndexDescription(
1720 Table => $args{'Table'}, Columns => [@{$args{'Columns'}}, @{$args{'Optional'}}],
1726 sub DropIndexesThatArePrefix {
1728 my %args = ( Table => undef, Columns => [], @_ );
1730 my @list = $self->IndexesThatBeginWith(
1731 Table => $args{'Table'}, Columns => [$args{'Columns'}[0]],
1734 my $checking = join ',', map lc $_, @{ $args{'Columns'} }, '';
1735 foreach my $i ( splice @list ) {
1736 my $columns = join ',', @{ $i->{'Columns'} }, '';
1737 next unless $checking =~ /^\Q$columns/i;
1743 foreach my $i ( @list ) {
1744 my ($status, $msg) = $self->DropIndex(
1745 Table => $i->{'Table'}, Name => $i->{'Name'},
1747 my $method = $status ? 'debug' : 'warning';
1748 RT->Logger->$method($msg);
1752 # log a mason stack trace instead of a Carp::longmess because it's less painful
1753 # and uses mason component paths properly
1754 sub _LogSQLStatement {
1756 my $statement = shift;
1757 my $duration = shift;
1760 require HTML::Mason::Exceptions;
1761 push @{$self->{'StatementLog'}} , ([Time::HiRes::time(), $statement, [@bind], $duration, HTML::Mason::Exception->new->as_string]);
1764 # helper in a few cases where we do SQL by hand
1765 sub __MakeClauseCaseInsensitive {
1767 return join ' ', @_ unless $self->CaseSensitive;
1768 my ($field, $op, $value) = $self->_MakeClauseCaseInsensitive(@_);
1769 return "$field $op $value";
1774 my $dbh = shift || $self->dbh;
1779 $dbh->{Driver}->{Name} eq 'Pg'
1780 && $dbh->{'pg_server_version'} >= 90200
1781 && !eval { DBD::Pg->VERSION('2.19.3'); 1 }
1783 die "You're using PostgreSQL 9.2 or newer. You have to upgrade DBD::Pg module to 2.19.3 or newer: $@";
1789 my $sth = $dbh->table_info( '', undef, undef, "'TABLE'");
1790 while ( my $table = $sth->fetchrow_hashref ) {
1791 push @res, $table->{TABLE_NAME} || $table->{table_name};
1797 __PACKAGE__->FinalizeDatabaseType;
1799 RT::Base->_ImportOverlays();