summaryrefslogtreecommitdiff
path: root/rt/etc/upgrade
diff options
context:
space:
mode:
authorivan <ivan>2009-12-31 13:16:41 +0000
committerivan <ivan>2009-12-31 13:16:41 +0000
commitb4b0c7e72d7eaee2fbfc7022022c9698323203dd (patch)
treeba4cd21399e412c32fe3737eaa8478e3271509f9 /rt/etc/upgrade
parent2dfda73eeb3eae2d4f894099754794ef07d060dd (diff)
import rt 3.8.7
Diffstat (limited to 'rt/etc/upgrade')
-rwxr-xr-xrt/etc/upgrade/3.1.0/acl.Pg2
-rw-r--r--rt/etc/upgrade/3.3.0/acl.Pg2
-rw-r--r--rt/etc/upgrade/3.5.1/content2
-rw-r--r--rt/etc/upgrade/3.7.1/content14
-rw-r--r--rt/etc/upgrade/3.7.10/content49
-rw-r--r--rt/etc/upgrade/3.7.15/content12
-rw-r--r--rt/etc/upgrade/3.7.19/content37
-rw-r--r--rt/etc/upgrade/3.7.3/schema.Oracle5
-rw-r--r--rt/etc/upgrade/3.7.3/schema.Pg1
-rw-r--r--rt/etc/upgrade/3.7.3/schema.mysql1
-rw-r--r--rt/etc/upgrade/3.7.81/schema.Oracle2
-rw-r--r--rt/etc/upgrade/3.7.81/schema.mysql2
-rw-r--r--rt/etc/upgrade/3.7.82/content13
-rw-r--r--rt/etc/upgrade/3.7.85/content11
-rw-r--r--rt/etc/upgrade/3.7.86/content23
-rw-r--r--rt/etc/upgrade/3.7.87/content28
-rwxr-xr-xrt/etc/upgrade/3.8-branded-queues-extension95
-rwxr-xr-xrt/etc/upgrade/3.8-branded-queues-extension.in95
-rwxr-xr-xrt/etc/upgrade/3.8-ical-extension96
-rw-r--r--rt/etc/upgrade/3.8-ical-extension.in96
-rw-r--r--rt/etc/upgrade/3.8.0/content22
-rw-r--r--rt/etc/upgrade/3.8.1/content24
-rw-r--r--rt/etc/upgrade/3.8.2/content186
-rw-r--r--rt/etc/upgrade/3.8.3/content91
-rw-r--r--rt/etc/upgrade/3.8.3/schema.Pg3
-rw-r--r--rt/etc/upgrade/3.8.4/content59
-rw-r--r--rt/etc/upgrade/3.8.6/content10
-rw-r--r--rt/etc/upgrade/shrink_cgm_table.pl72
-rwxr-xr-xrt/etc/upgrade/split-out-cf-categories171
-rw-r--r--rt/etc/upgrade/split-out-cf-categories.in171
-rwxr-xr-xrt/etc/upgrade/upgrade-mysql-schema.pl390
31 files changed, 1782 insertions, 3 deletions
diff --git a/rt/etc/upgrade/3.1.0/acl.Pg b/rt/etc/upgrade/3.1.0/acl.Pg
index 809e99a..9c88782 100755
--- a/rt/etc/upgrade/3.1.0/acl.Pg
+++ b/rt/etc/upgrade/3.1.0/acl.Pg
@@ -11,7 +11,7 @@ sub acl {
foreach my $table (@tables) {
push @acls,
"GRANT SELECT, INSERT, UPDATE, DELETE ON $table to "
- . $RT::DatabaseUser . ";";
+ . RT->Config->Get('DatabaseUser') . ";";
}
return (@acls);
diff --git a/rt/etc/upgrade/3.3.0/acl.Pg b/rt/etc/upgrade/3.3.0/acl.Pg
index 2069a19..bd2e36c 100644
--- a/rt/etc/upgrade/3.3.0/acl.Pg
+++ b/rt/etc/upgrade/3.3.0/acl.Pg
@@ -12,7 +12,7 @@ sub acl {
foreach my $table (@tables) {
push @acls,
"GRANT SELECT, INSERT, UPDATE, DELETE ON $table to "
- . $RT::DatabaseUser . ";";
+ . RT->Config->Get('DatabaseUser') . ";";
}
return (@acls);
diff --git a/rt/etc/upgrade/3.5.1/content b/rt/etc/upgrade/3.5.1/content
index e3898a7..02d6a0c 100644
--- a/rt/etc/upgrade/3.5.1/content
+++ b/rt/etc/upgrade/3.5.1/content
@@ -2,7 +2,7 @@
{ Name => 'Search - My Tickets',
Description => '[_1] highest priority tickets I own',
Content =>
- { Format => "'<a href=\"__WebPath__/Ticket/Display.html?id=__id__\">__id__</a>/TITLE:#', '<a href=\"__WebPath__/Ticket/Display.html?id=__id__\">__Subject__</a>/TITLE:Subject', Priority, QueueName, ExtendedStatus",
+ { Format => q{'<a href="__WebPath__/Ticket/Display.html?id=__id__">__id__</a>/TITLE:#', '<a href="__WebPath__/Ticket/Display.html?id=__id__">__Subject__</a>/TITLE:Subject', Priority, QueueName, ExtendedStatus},
Query => " Owner = '__CurrentUser__' AND ( Status = 'new' OR Status = 'open')",
OrderBy => 'Priority',
Order => 'DESC' },
diff --git a/rt/etc/upgrade/3.7.1/content b/rt/etc/upgrade/3.7.1/content
new file mode 100644
index 0000000..fdd5061
--- /dev/null
+++ b/rt/etc/upgrade/3.7.1/content
@@ -0,0 +1,14 @@
+@ScripConditions = (
+ { Name => 'On Close', # loc
+ Description => 'Whenever a ticket is closed', # loc
+ ApplicableTransTypes => 'Status,Set',
+ ExecModule => 'CloseTicket',
+ },
+ { Name => 'On Reopen', # loc
+ Description => 'Whenever a ticket is reopened', # loc
+ ApplicableTransTypes => 'Status,Set',
+ ExecModule => 'ReopenTicket',
+ },
+);
+
+
diff --git a/rt/etc/upgrade/3.7.10/content b/rt/etc/upgrade/3.7.10/content
new file mode 100644
index 0000000..d19f9e6
--- /dev/null
+++ b/rt/etc/upgrade/3.7.10/content
@@ -0,0 +1,49 @@
+
+@Templates = (
+ { Queue => 0,
+ Name => "Error: public key", # loc
+ Description =>
+ "Inform user that he has problems with public key and couldn't recieve encrypted content", # loc
+ Content => q{Subject: We have no your public key or it's wrong
+
+You received this message as we have no your public PGP key or we have a problem with your key. Inform the administrator about the problem.
+}
+ },
+ { Queue => 0,
+ Name => "Error to RT owner: public key", # loc
+ Description =>
+ "Inform RT owner that user(s) have problems with public keys", # loc
+ Content => q{Subject: Some users have problems with public keys
+
+You received this message as RT has problems with public keys of the following user:
+{
+ foreach my $e ( @BadRecipients ) {
+ $OUT .= "* ". $e->{'Message'} ."\n";
+ }
+}}
+ },
+ { Queue => 0,
+ Name => "Error: no private key", # loc
+ Description =>
+ "Inform user that we received an encrypted email and we have no private keys to decrypt", # loc
+ Content => q{Subject: we received message we cannot decrypt
+
+You sent an encrypted message with subject '{ $Message->head->get('Subject') }',
+but we have no private key it's encrypted to.
+
+Please, check that you encrypt messages with correct keys
+or contact the system administrator.}
+ },
+ { Queue => 0,
+ Name => "Error: bad GnuPG data", # loc
+ Description =>
+ "Inform user that a message he sent has invalid GnuPG data", # loc
+ Content => q{Subject: We received a message we cannot handle
+
+You sent us a message that we cannot handle due to corrupted GnuPG signature or encrypted block. we get the following error(s):
+{ foreach my $msg ( @Messages ) {
+ $OUT .= "* $msg\n";
+ }
+}}
+ },
+);
diff --git a/rt/etc/upgrade/3.7.15/content b/rt/etc/upgrade/3.7.15/content
new file mode 100644
index 0000000..9d97c35
--- /dev/null
+++ b/rt/etc/upgrade/3.7.15/content
@@ -0,0 +1,12 @@
+
+@Templates = (
+ { Queue => 0,
+ Name => "Forward", # loc
+ Description => "Heading of a forwarded message", # loc
+ Content => q{
+
+This is forward of transaction #{ $Transaction->id } of a ticket #{ $Ticket->id }
+}
+ },
+);
+
diff --git a/rt/etc/upgrade/3.7.19/content b/rt/etc/upgrade/3.7.19/content
new file mode 100644
index 0000000..ac34ebe
--- /dev/null
+++ b/rt/etc/upgrade/3.7.19/content
@@ -0,0 +1,37 @@
+
+{ use strict;
+add_description_to_all_scrips();
+
+sub add_description_to_all_scrips {
+ require RT::Scrips;
+ my $scrips = RT::Scrips->new( $RT::SystemUser );
+ $scrips->Limit( FIELD => 'Description', OPERATOR => 'IS', VALUE => 'NULL' );
+ $scrips->Limit( FIELD => 'Description', VALUE => '' );
+ while ( my $scrip = $scrips->Next ) {
+ my $desc = $scrip->Description;
+ next if defined $desc && length $desc;
+
+ $desc = gen_scrip_description( $scrip );
+
+ my ($status, $msg) = $scrip->SetDescription( $desc );
+ unless ( $status ) {
+ print STDERR "Couldn't set description of a scrip: $msg";
+ } else {
+ print "Added description to scrip #". $scrip->id ."\n";
+ }
+ }
+}
+
+sub gen_scrip_description {
+ my $scrip = shift;
+ my $condition = $scrip->ConditionObj->Name
+ || $scrip->ConditionObj->Description
+ || ('On Condition #'. $scrip->Condition);
+ my $action = $scrip->ActionObj->Name
+ || $scrip->ActionObj->Description
+ || ('Run Action #'. $scrip->Action);
+ return join ' ', $condition, $action;
+}
+}
+
+1;
diff --git a/rt/etc/upgrade/3.7.3/schema.Oracle b/rt/etc/upgrade/3.7.3/schema.Oracle
new file mode 100644
index 0000000..6136efa
--- /dev/null
+++ b/rt/etc/upgrade/3.7.3/schema.Oracle
@@ -0,0 +1,5 @@
+alter table CustomFields add Pattern_TMP clob;
+update CustomFields set Pattern_TMP = Pattern;
+commit;
+alter table CustomFields drop column Pattern;
+alter table CustomFields rename column Pattern_TMP to Pattern;
diff --git a/rt/etc/upgrade/3.7.3/schema.Pg b/rt/etc/upgrade/3.7.3/schema.Pg
new file mode 100644
index 0000000..5d0312e
--- /dev/null
+++ b/rt/etc/upgrade/3.7.3/schema.Pg
@@ -0,0 +1 @@
+ALTER TABLE customfields ALTER COLUMN pattern TYPE VARCHAR(65536);
diff --git a/rt/etc/upgrade/3.7.3/schema.mysql b/rt/etc/upgrade/3.7.3/schema.mysql
new file mode 100644
index 0000000..51c376d
--- /dev/null
+++ b/rt/etc/upgrade/3.7.3/schema.mysql
@@ -0,0 +1 @@
+ALTER TABLE CustomFields CHANGE Pattern Pattern TEXT NULL;
diff --git a/rt/etc/upgrade/3.7.81/schema.Oracle b/rt/etc/upgrade/3.7.81/schema.Oracle
new file mode 100644
index 0000000..02da4ec
--- /dev/null
+++ b/rt/etc/upgrade/3.7.81/schema.Oracle
@@ -0,0 +1,2 @@
+CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId);
+
diff --git a/rt/etc/upgrade/3.7.81/schema.mysql b/rt/etc/upgrade/3.7.81/schema.mysql
new file mode 100644
index 0000000..02da4ec
--- /dev/null
+++ b/rt/etc/upgrade/3.7.81/schema.mysql
@@ -0,0 +1,2 @@
+CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId);
+
diff --git a/rt/etc/upgrade/3.7.82/content b/rt/etc/upgrade/3.7.82/content
new file mode 100644
index 0000000..a1c555f
--- /dev/null
+++ b/rt/etc/upgrade/3.7.82/content
@@ -0,0 +1,13 @@
+@Attributes = (
+ { Name => 'Search - Bookmarked Tickets',
+ Description => 'Bookmarked Tickets', #loc
+ Content =>
+ { Format => q{'<a href="__WebPath__/Ticket/Display.html?id=__id__">__id__</a>/TITLE:#',}
+ . q{'<a href="__WebPath__/Ticket/Display.html?id=__id__">__Subject__</a>/TITLE:Subject',}
+ . q{Priority, QueueName, ExtendedStatus, Bookmark},
+ Query => "__Bookmarks__",
+ OrderBy => 'LastUpdated',
+ Order => 'DESC' },
+ },
+);
+
diff --git a/rt/etc/upgrade/3.7.85/content b/rt/etc/upgrade/3.7.85/content
new file mode 100644
index 0000000..49ab286
--- /dev/null
+++ b/rt/etc/upgrade/3.7.85/content
@@ -0,0 +1,11 @@
+@Templates = (
+
+ { Queue => '0',
+ Name => 'Email Digest', # loc
+ Description => 'Email template for periodic notification digests', # loc
+ Content => q[Subject: RT Email Digest
+
+{ $Argument }
+],
+ },
+);
diff --git a/rt/etc/upgrade/3.7.86/content b/rt/etc/upgrade/3.7.86/content
new file mode 100644
index 0000000..da087ed
--- /dev/null
+++ b/rt/etc/upgrade/3.7.86/content
@@ -0,0 +1,23 @@
+@Final = (
+ sub {
+ $RT::Logger->debug("Adding search for bookmarked tickets to defaults");
+ my $sys = RT::System->new($RT::SystemUser);
+
+ my $attrs = RT::Attributes->new( $RT::SystemUser );
+ $attrs->LimitToObject( $sys );
+ my ($attr) = $attrs->Named( 'HomepageSettings' );
+ unless ($attr) {
+ $RT::Logger->error("You have no global home page settings");
+ return;
+ }
+ my $content = $attr->Content;
+ unshift @{ $content->{'body'} ||= [] },
+ { type => 'system', name => 'Bookmarked Tickets' };
+
+ my ($status, $msg) = $attr->SetContent( $content );
+ $RT::Logger->error($msg) unless $status;
+
+ $RT::Logger->debug("done.");
+ return 1;
+ },
+);
diff --git a/rt/etc/upgrade/3.7.87/content b/rt/etc/upgrade/3.7.87/content
new file mode 100644
index 0000000..0c677c4
--- /dev/null
+++ b/rt/etc/upgrade/3.7.87/content
@@ -0,0 +1,28 @@
+@Templates = (
+{
+ Queue => 0,
+ Name => "Error: Missing dashboard", # loc
+ Description =>
+ "Inform user that a dashboard he subscribed to is missing", # loc
+ Content => q{Subject: [{RT->Config->Get('rtname')}] Missing dashboard!
+
+Greetings,
+
+You are subscribed to a dashboard that is currently missing. Most likely, the dashboard was deleted.
+
+RT will remove this subscription as it is no longer useful. Here's the information RT had about your subscription:
+
+DashboardID: { $SubscriptionObj->SubValue('DashboardId') }
+Frequency: { $SubscriptionObj->SubValue('Frequency') }
+Hour: { $SubscriptionObj->SubValue('Hour') }
+{
+ $SubscriptionObj->SubValue('Frequency') eq 'weekly'
+ ? "Day of week: " . $SubscriptionObj->SubValue('Dow')
+ : $SubscriptionObj->SubValue('Frequency') eq 'monthly'
+ ? "Day of month: " . $SubscriptionObj->SubValue('Dom')
+ : ''
+}
+}
+},
+);
+
diff --git a/rt/etc/upgrade/3.8-branded-queues-extension b/rt/etc/upgrade/3.8-branded-queues-extension
new file mode 100755
index 0000000..6a0ea48
--- /dev/null
+++ b/rt/etc/upgrade/3.8-branded-queues-extension
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC
+# <jesse@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;
+
+use lib "local/lib";
+use lib "lib";
+
+
+use RT;
+RT::LoadConfig();
+RT::Init();
+
+use RT::Queues;
+
+my $queues = RT::Queues->new( $RT::SystemUser );
+$queues->UnLimit();
+while ( my $queue = $queues->Next ) {
+ print "Processing queue ". ($queue->Name || $queue->id) ."...\n";
+ my $old_attr = $queue->FirstAttribute('BrandedSubjectTag');
+ unless ( $old_attr ) {
+ print "\thas no old-style subject tag. skipping\n";
+ next;
+ }
+ my $old_value = $old_attr->Content;
+ unless ( $old_value ) {
+ print "\thas empty old-style subject tag\n";
+ } else {
+ my ($status, $msg) = $queue->SetSubjectTag( $old_value );
+ unless ( $status ) {
+ print STDERR "\tERROR. Couldn't set tag: $msg\n";
+ next;
+ } else {
+ print "\thave set new-style subject tag to '$old_value'\n";
+ }
+ }
+
+ my ($status, $msg) = $queue->DeleteAttribute('BrandedSubjectTag');
+ unless ( $status ) {
+ print STDERR "\tERROR. Couldn't delete old-style tag: $msg\n";
+ next;
+ } else {
+ print "\tdeleted old-style tag entry\n";
+ }
+ print "\tDONE\n";
+}
+
+exit 0;
+
diff --git a/rt/etc/upgrade/3.8-branded-queues-extension.in b/rt/etc/upgrade/3.8-branded-queues-extension.in
new file mode 100755
index 0000000..2f07d6e
--- /dev/null
+++ b/rt/etc/upgrade/3.8-branded-queues-extension.in
@@ -0,0 +1,95 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC
+# <jesse@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;
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+
+use RT;
+RT::LoadConfig();
+RT::Init();
+
+use RT::Queues;
+
+my $queues = RT::Queues->new( $RT::SystemUser );
+$queues->UnLimit();
+while ( my $queue = $queues->Next ) {
+ print "Processing queue ". ($queue->Name || $queue->id) ."...\n";
+ my $old_attr = $queue->FirstAttribute('BrandedSubjectTag');
+ unless ( $old_attr ) {
+ print "\thas no old-style subject tag. skipping\n";
+ next;
+ }
+ my $old_value = $old_attr->Content;
+ unless ( $old_value ) {
+ print "\thas empty old-style subject tag\n";
+ } else {
+ my ($status, $msg) = $queue->SetSubjectTag( $old_value );
+ unless ( $status ) {
+ print STDERR "\tERROR. Couldn't set tag: $msg\n";
+ next;
+ } else {
+ print "\thave set new-style subject tag to '$old_value'\n";
+ }
+ }
+
+ my ($status, $msg) = $queue->DeleteAttribute('BrandedSubjectTag');
+ unless ( $status ) {
+ print STDERR "\tERROR. Couldn't delete old-style tag: $msg\n";
+ next;
+ } else {
+ print "\tdeleted old-style tag entry\n";
+ }
+ print "\tDONE\n";
+}
+
+exit 0;
+
diff --git a/rt/etc/upgrade/3.8-ical-extension b/rt/etc/upgrade/3.8-ical-extension
new file mode 100755
index 0000000..9561b97
--- /dev/null
+++ b/rt/etc/upgrade/3.8-ical-extension
@@ -0,0 +1,96 @@
+#!/usr/bin/perl
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC
+# <jesse@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;
+
+use lib "local/lib";
+use lib "lib";
+
+
+use RT;
+RT::LoadConfig();
+RT::Init();
+
+use RT::Attributes;
+my $attrs = RT::Attributes->new( $RT::SystemUser );
+$attrs->Limit(FIELD => 'ObjectType', OPERATOR=> '=', VALUE => 'RT::User');
+$attrs->Limit(FIELD => 'Name', OPERATOR=> '=', VALUE => 'ical-auth-token');
+while ( my $attr = $attrs->Next ) {
+ my $uid = $attr->ObjectId;
+ print "Processing auth token of user #". $uid ."...\n";
+
+ my $user = RT::User->new( $RT::SystemUser );
+ $user->Load( $uid );
+ unless ( $user->id ) {
+ print STDERR "\tERROR. Couldn't load user record\n";
+ next;
+ }
+
+ my ($status, $msg);
+
+ ($status, $msg) = $user->DeleteAttribute('AuthToken')
+ if $user->FirstAttribute('AuthToken');
+ unless ( $status ) {
+ print STDERR "\tERROR. Couldn't delete duplicated attribute: $msg\n";
+ next;
+ } else {
+ print "\tdeleted duplicate attribute\n";
+ }
+
+ ($status, $msg) = $attr->SetName('AuthToken');
+ unless ( $status ) {
+ print STDERR "\tERROR. Couldn't rename attribute: $msg\n";
+ next;
+ } else {
+ print "\trenamed attribute\n";
+ }
+ print "\tDONE\n";
+}
+
+exit 0;
diff --git a/rt/etc/upgrade/3.8-ical-extension.in b/rt/etc/upgrade/3.8-ical-extension.in
new file mode 100644
index 0000000..510419c
--- /dev/null
+++ b/rt/etc/upgrade/3.8-ical-extension.in
@@ -0,0 +1,96 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC
+# <jesse@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;
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+
+use RT;
+RT::LoadConfig();
+RT::Init();
+
+use RT::Attributes;
+my $attrs = RT::Attributes->new( $RT::SystemUser );
+$attrs->Limit(FIELD => 'ObjectType', OPERATOR=> '=', VALUE => 'RT::User');
+$attrs->Limit(FIELD => 'Name', OPERATOR=> '=', VALUE => 'ical-auth-token');
+while ( my $attr = $attrs->Next ) {
+ my $uid = $attr->ObjectId;
+ print "Processing auth token of user #". $uid ."...\n";
+
+ my $user = RT::User->new( $RT::SystemUser );
+ $user->Load( $uid );
+ unless ( $user->id ) {
+ print STDERR "\tERROR. Couldn't load user record\n";
+ next;
+ }
+
+ my ($status, $msg);
+
+ ($status, $msg) = $user->DeleteAttribute('AuthToken')
+ if $user->FirstAttribute('AuthToken');
+ unless ( $status ) {
+ print STDERR "\tERROR. Couldn't delete duplicated attribute: $msg\n";
+ next;
+ } else {
+ print "\tdeleted duplicate attribute\n";
+ }
+
+ ($status, $msg) = $attr->SetName('AuthToken');
+ unless ( $status ) {
+ print STDERR "\tERROR. Couldn't rename attribute: $msg\n";
+ next;
+ } else {
+ print "\trenamed attribute\n";
+ }
+ print "\tDONE\n";
+}
+
+exit 0;
diff --git a/rt/etc/upgrade/3.8.0/content b/rt/etc/upgrade/3.8.0/content
new file mode 100644
index 0000000..f4e389b
--- /dev/null
+++ b/rt/etc/upgrade/3.8.0/content
@@ -0,0 +1,22 @@
+@Final = (
+ # by incident we've changed 'My Bookmarks' to 'Bookmarked Tickets' when
+ # 3.7.82 upgrade script still was creating 'My Bookmarks', try to fix it
+ sub {
+ $RT::Logger->debug("Going to rename 'My Bookmarks' to 'Bookmarked Tickets'");
+ my $sys = RT::System->new($RT::SystemUser);
+
+ my $attrs = RT::Attributes->new( $RT::SystemUser );
+ $attrs->LimitToObject( $sys );
+ my ($attr) = $attrs->Named( 'Search - My Bookmarks' );
+ unless ($attr) {
+ $RT::Logger->debug("You have no global search 'My Bookmarks'. Skipped.");
+ return 1;
+ }
+ my ($status, $msg) = $attr->SetName( 'Search - Bookmarked Tickets' );
+ $RT::Logger->error($msg) and return undef unless $status;
+
+ $RT::Logger->debug("Renamed.");
+ return 1;
+ },
+);
+
diff --git a/rt/etc/upgrade/3.8.1/content b/rt/etc/upgrade/3.8.1/content
new file mode 100644
index 0000000..89db596
--- /dev/null
+++ b/rt/etc/upgrade/3.8.1/content
@@ -0,0 +1,24 @@
+@Final = (
+ sub {
+ $RT::Logger->debug("Going to adjust 'Bookmarked Tickets'");
+ my $sys = RT::System->new($RT::SystemUser);
+
+ my $attrs = RT::Attributes->new( $RT::SystemUser );
+ $attrs->LimitToObject( $sys );
+ my ($attr) = $attrs->Named( 'Search - Bookmarked Tickets' );
+ unless ($attr) {
+ $RT::Logger->debug("You have no global search 'Bookmarked Tickets'. Skipped.");
+ return 1;
+ }
+ my $props = $attr->Content;
+ $props->{'Query'} =~ s/__Bookmarks__/id = '__Bookmarked__'/g;
+
+ my ($status, $msg) = $attr->SetContent( $props );
+ $RT::Logger->error($msg) and return undef unless $status;
+
+ $RT::Logger->debug("Fixed.");
+ return 1;
+ },
+);
+
+
diff --git a/rt/etc/upgrade/3.8.2/content b/rt/etc/upgrade/3.8.2/content
new file mode 100644
index 0000000..9bd92be
--- /dev/null
+++ b/rt/etc/upgrade/3.8.2/content
@@ -0,0 +1,186 @@
+@Initial = (
+ sub {
+ $RT::Logger->warning(
+ "Going to add [OLD] prefix to all temlates in approvals queue."
+ ." If you never used approvals then you can delete all these"
+ ." templates with [OLD] prefix. Leave new there may be you will"
+ ." want to use approvals some time."
+ );
+
+ my $approvals_q = RT::Queue->new( $RT::SystemUser );
+ $approvals_q->Load('___Approvals');
+ unless ( $approvals_q->id ) {
+ $RT::Logger->error("You have no approvals queue.");
+ return 1;
+ }
+
+ my $templates = RT::Templates->new( $RT::SystemUser );
+ $templates->LimitToQueue( $approvals_q->id );
+ while ( my $tmpl = $templates->Next ) {
+ my ($status, $msg) = $tmpl->SetName( "[OLD] ". $tmpl->Name );
+ unless ( $status ) {
+ $RT::Logger->error("Couldn't rename template #". $tmpl->id .": $msg");
+ }
+ }
+ return 1;
+ },
+);
+@ACL = (
+ { GroupDomain => 'SystemInternal',
+ GroupType => 'privileged',
+ Right => 'ShowApprovalsTab', },
+);
+
+@Templates = (
+ { Queue => '___Approvals',
+ Name => "New Pending Approval", # loc
+ Description =>
+ "Notify Owners and AdminCcs of new items pending their approval", # loc
+ Content => 'Subject: New Pending Approval: {$Ticket->Subject}
+
+Greetings,
+
+There is a new item pending your approval: "{$Ticket->Subject()}",
+a summary of which appears below.
+
+Please visit {RT->Config->Get(\'WebURL\')}Approvals/Display.html?id={$Ticket->id}
+to approve or reject this ticket, or {RT->Config->Get(\'WebURL\')}Approvals/ to
+batch-process all your pending approvals.
+
+-------------------------------------------------------------------------
+{$Transaction->Content()}
+'
+ },
+ { Queue => '___Approvals',
+ Name => "Approval Passed", # loc
+ Description =>
+ "Notify Requestor of their ticket has been approved by some approver", # loc
+ Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Other approvals may be pending.
+
+Approver\'s notes: { $Notes }
+'
+ },
+ { Queue => '___Approvals',
+ Name => "All Approvals Passed", # loc
+ Description =>
+ "Notify Requestor of their ticket has been approved by all approvers", # loc
+ Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Its Owner may now start to act on it.
+
+Approver\'s notes: { $Notes }
+'
+ },
+ { Queue => '___Approvals',
+ Name => "Approval Rejected", # loc
+ Description =>
+ "Notify Owner of their rejected ticket", # loc
+ Content => 'Subject: Ticket Rejected: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been rejected by { eval { $Approval->OwnerObj->Name } }.
+
+Approver\'s notes: { $Notes }
+'
+ },
+ { Queue => '___Approvals',
+ Name => "Approval Ready for Owner", # loc
+ Description =>
+ "Notify Owner of their ticket has been approved and is ready to be acted on", # loc
+ Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+The ticket has been approved, you may now start to act on it.
+
+'
+ },
+);
+
+@Final = (
+ sub {
+ $RT::Logger->debug("Going to adjust dashboards");
+ my $sys = RT::System->new($RT::SystemUser);
+
+ my $attrs = RT::Attributes->new( $RT::SystemUser );
+ $attrs->UnLimit;
+ my @dashboards = $attrs->Named('Dashboard');
+
+ if (@dashboards == 0) {
+ $RT::Logger->debug("You have no dashboards. Skipped.");
+ return 1;
+ }
+
+ for my $attr (@dashboards) {
+ my $props = $attr->Content;
+ if (exists $props->{Searches}) {
+ $props->{Panes} = {
+ body => [
+ map {
+ my ($privacy, $id, $desc) = @$_;
+
+ {
+ portlet_type => 'search',
+ privacy => $privacy,
+ id => $id,
+ description => $desc,
+ pane => 'body',
+ }
+ } @{ delete $props->{Searches} }
+ ],
+ };
+ }
+ my ($status, $msg) = $attr->SetContent( $props );
+ $RT::Logger->error($msg) unless $status;
+ }
+
+ $RT::Logger->debug("Fixed.");
+ return 1;
+ },
+ sub {
+ my $approvals_q = RT::Queue->new( $RT::SystemUser );
+ $approvals_q->Load('___Approvals');
+ unless ( $approvals_q->id ) {
+ $RT::Logger->error("You have no approvals queue.");
+ return 1;
+ }
+
+ require File::Temp;
+ my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( 'rt-approvals-scrips-XXXX', CLEANUP => 0 );
+ unless ( $tmp_fh ) {
+ $RT::Logger->error("Couldn't create temporary file.");
+ return 0;
+ }
+
+ $RT::Logger->warning(
+ "IMPORTANT: We're going to delete all scrips in Approvals queue"
+ ." and save them in '$tmp_fn' file."
+ );
+
+ require Data::Dumper;
+
+ my $scrips = RT::Scrips->new( $RT::SystemUser );
+ $scrips->LimitToQueue( $approvals_q->id );
+ while ( my $scrip = $scrips->Next ) {
+ my %tmp =
+ map { $tmp->{ $_ } = $scrip->_Value( $_ ) }
+ $scrip->ReadableAttributes;
+
+ print $tmp_fh Data::Dumper::Dumper( \%tmp );
+
+ my ($status, $msg) = $scrip->Delete;
+ unless ( $status ) {
+ $RT::Logger->error( "Couldn't delete scrip: $msg");
+ }
+ }
+ },
+);
diff --git a/rt/etc/upgrade/3.8.3/content b/rt/etc/upgrade/3.8.3/content
new file mode 100644
index 0000000..8538e42
--- /dev/null
+++ b/rt/etc/upgrade/3.8.3/content
@@ -0,0 +1,91 @@
+@ScripConditions = (
+ { Name => 'On Reject', # loc
+ Description => 'Whenever a ticket is rejected', # loc
+ ApplicableTransTypes => 'Status',
+ ExecModule => 'StatusChange',
+ Argument => 'rejected'
+
+ },
+);
+
+@Final = (
+ sub {
+ $RT::Logger->debug("Going to correct descriptions of notify actions in the DB");
+
+ my $actions = RT::ScripActions->new( $RT::SystemUser );
+ $actions->Limit(
+ FIELD => 'ExecModule',
+ VALUE => 'Notify',
+ );
+ $actions->Limit(
+ FIELD => 'Argument',
+ VALUE => 'All',
+ );
+ while ( my $action = $actions->Next ) {
+ my ($status, $msg) = $action->__Set( Field => 'Name', Value => 'Notify Owner, Requestors, Ccs and AdminCcs' );
+ $RT::Logger->warning( "Couldn't change action name: $msg" )
+ unless $status;
+
+ ($status, $msg) = $action->__Set( Field => 'Description', Value => 'Send mail to owner and all watchers' );
+ $RT::Logger->warning( "Couldn't change action description: $msg" )
+ unless $status;
+ }
+
+ $actions = RT::ScripActions->new( $RT::SystemUser );
+ $actions->Limit(
+ FIELD => 'ExecModule',
+ VALUE => 'NotifyAsComment',
+ );
+ $actions->Limit(
+ FIELD => 'Argument',
+ VALUE => 'All',
+ );
+ while ( my $action = $actions->Next ) {
+ my ($status, $msg) = $action->__Set( Field => 'Name', Value => 'Notify Owner, Requestors, Ccs and AdminCcs as Comment' );
+ $RT::Logger->warning( "Couldn't change action name: $msg" )
+ unless $status;
+
+ ($status, $msg) = $action->__Set( Field => 'Description', Value => 'Send mail to owner and all watchers as a "comment"' );
+ $RT::Logger->warning( "Couldn't change action description: $msg" )
+ unless $status;
+ }
+
+ $RT::Logger->debug("Corrected descriptions of notify actions in the DB.");
+ return 1;
+ },
+);
+
+
+{
+$RT::Logger->debug("Going to add in Extract Subject Tag actions if they were missed during a previous upgrade");
+
+$actions = RT::ScripActions->new( $RT::SystemUser );
+$actions->Limit(
+ FIELD => 'ExecModule',
+ VALUE => 'ExtractSubjectTag',
+);
+my $extract_action = $actions->First;
+
+if ( $extract_action && $extract_action->Id ) {
+ $RT::Logger->debug("You appear to already have an Extract Subject Tag action, skipping");
+ return 1;
+} else {
+ $RT::Logger->debug("Didn't find an existing Extract Subject Tag action, adding it");
+ push @ScripActions, (
+ { Name => 'Extract Subject Tag', # loc
+ Description => 'Extract tags from a Transaction\'s subject and add them to the Ticket\'s subject.', # loc
+ ExecModule => 'ExtractSubjectTag'
+ },
+ );
+
+ $RT::Logger->debug("Adding Extract Subject Tag Scrip");
+ push @Scrips, (
+ { Description => "On transaction, add any tags in the transaction's subject to the ticket's subject",
+ ScripCondition => 'On Transaction',
+ ScripAction => 'Extract Subject Tag',
+ Template => 'Blank'
+ },
+ );
+}
+}
+
diff --git a/rt/etc/upgrade/3.8.3/schema.Pg b/rt/etc/upgrade/3.8.3/schema.Pg
new file mode 100644
index 0000000..bbe5536
--- /dev/null
+++ b/rt/etc/upgrade/3.8.3/schema.Pg
@@ -0,0 +1,3 @@
+
+CREATE UNIQUE INDEX GroupMembers1 ON GroupMembers(GroupId, MemberId);
+
diff --git a/rt/etc/upgrade/3.8.4/content b/rt/etc/upgrade/3.8.4/content
new file mode 100644
index 0000000..be5a6bf
--- /dev/null
+++ b/rt/etc/upgrade/3.8.4/content
@@ -0,0 +1,59 @@
+
+@Final = (
+ sub {
+ $RT::Logger->debug("Going to correct arguments of NotifyGroup actions if you have any");
+ use strict;
+
+ my $actions = RT::ScripActions->new( $RT::SystemUser );
+ $actions->Limit(
+ FIELD => 'ExecModule',
+ VALUE => 'NotifyGroup',
+ );
+ $actions->Limit(
+ FIELD => 'ExecModule',
+ VALUE => 'NotifyGroupAsComment',
+ );
+
+ my $converter = sub {
+ my $arg = shift;
+ my @res;
+ foreach my $r ( @{ $arg } ) {
+ my $obj;
+ next unless $r->{'Type'};
+ if( lc $r->{'Type'} eq 'user' ) {
+ $obj = RT::User->new( $RT::SystemUser );
+ } elsif ( lc $r->{'Type'} eq 'group' ) {
+ $obj = RT::Group->new( $RT::SystemUser );
+ } else {
+ next;
+ }
+ $obj->Load( $r->{'Instance'} );
+ my $id = $obj->id;
+ next unless( $id );
+
+ push @res, $id;
+ }
+
+ return join ',', @res;
+ };
+
+ require Storable;
+ while ( my $action = $actions->Next ) {
+ my $argument = $action->Argument;
+ my $new = '';
+ local $@;
+ if ( my $struct = eval { Storable::thaw( $argument ) } ) {
+ $new = $converter->( $struct );
+ } else {
+ $new = join /, /, grep length, split /[^0-9]+/, $argument;
+ }
+ next if $new eq $argument;
+
+ my ($status, $msg) = $action->__Set( Field => 'Argument', Value => $new );
+ $RT::Logger->warning( "Couldn't change argument value of the action: $msg" )
+ unless $status;
+ }
+ },
+);
+
+
diff --git a/rt/etc/upgrade/3.8.6/content b/rt/etc/upgrade/3.8.6/content
new file mode 100644
index 0000000..a9793c6
--- /dev/null
+++ b/rt/etc/upgrade/3.8.6/content
@@ -0,0 +1,10 @@
+@Templates = (
+ { Queue => 0,
+ Name => "Forward Ticket", # loc
+ Description => "Heading of a forwarded Ticket", # loc
+ Content => q{
+
+This is a forward of ticket #{ $Ticket->id }
+}
+ },
+);
diff --git a/rt/etc/upgrade/shrink_cgm_table.pl b/rt/etc/upgrade/shrink_cgm_table.pl
new file mode 100644
index 0000000..3d153c7
--- /dev/null
+++ b/rt/etc/upgrade/shrink_cgm_table.pl
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+use 5.8.3;
+use strict;
+use warnings;
+
+use RT;
+RT::LoadConfig();
+RT->Config->Set('LogToScreen' => 'debug');
+RT::Init();
+
+use RT::CachedGroupMembers;
+my $cgms = RT::CachedGroupMembers->new( $RT::SystemUser );
+$cgms->Limit(
+ FIELD => 'id',
+ OPERATOR => '!=',
+ VALUE => 'main.Via',
+ QUOTEVALUE => 0,
+ ENTRYAGGREGATOR => 'AND',
+);
+$cgms->FindAllRows;
+
+my $alias = $cgms->Join(
+ TYPE => 'LEFT',
+ FIELD1 => 'Via',
+ TABLE2 => 'CachedGroupMembers',
+ FIELD2 => 'id',
+);
+$cgms->Limit(
+ ALIAS => $alias,
+ FIELD => 'MemberId',
+ OPERATOR => '=',
+ VALUE => $alias .'.GroupId',
+ QUOTEVALUE => 0,
+ ENTRYAGGREGATOR => 'AND',
+);
+$cgms->Limit(
+ ALIAS => $alias,
+ FIELD => 'id',
+ OPERATOR => '=',
+ VALUE => $alias .'.Via',
+ QUOTEVALUE => 0,
+ ENTRYAGGREGATOR => 'AND',
+);
+
+FetchNext( $cgms, 'init' );
+while ( my $rec = FetchNext( $cgms ) ) {
+ $RT::Handle->BeginTransaction;
+ my ($status) = $rec->Delete;
+ unless ($status) {
+ print STDERR "Couldn't delete CGM #". $rec->id;
+ exit 1;
+ }
+ $RT::Handle->Commit;
+}
+
+use constant PAGE_SIZE => 1000;
+sub FetchNext {
+ my ($objs, $init) = @_;
+ if ( $init ) {
+ $objs->RowsPerPage( PAGE_SIZE );
+ $objs->FirstPage;
+ return;
+ }
+
+ my $obj = $objs->Next;
+ return $obj if $obj;
+ $objs->RedoSearch;
+ $objs->FirstPage;
+ return $objs->Next;
+}
+
diff --git a/rt/etc/upgrade/split-out-cf-categories b/rt/etc/upgrade/split-out-cf-categories
new file mode 100755
index 0000000..d4077ce
--- /dev/null
+++ b/rt/etc/upgrade/split-out-cf-categories
@@ -0,0 +1,171 @@
+#!/usr/bin/perl
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC
+# <jesse@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;
+
+use lib "local/lib";
+use lib "lib";
+
+use RT;
+RT::LoadConfig();
+RT->Config->Set('LogToScreen' => 'debug');
+RT::Init();
+
+$| = 1;
+
+$RT::Handle->BeginTransaction();
+
+use RT::CustomFields;
+my $CFs = RT::CustomFields->new( $RT::SystemUser );
+$CFs->UnLimit;
+$CFs->Limit( FIELD => 'Type', VALUE => 'Select' );
+
+my $seen;
+while (my $cf = $CFs->Next ) {
+ next if $cf->BasedOnObj->Id;
+ my @categories;
+ my %mapping;
+ my $values = $cf->Values;
+ while (my $value = $values->Next) {
+ next unless defined $value->Category and length $value->Category;
+ push @categories, $value->Category unless grep {$_ eq $value->Category} @categories;
+ $mapping{$value->Name} = $value->Category;
+ }
+ next unless @categories;
+
+ $seen++;
+ print "Found CF '@{[$cf->Name]}' with categories:\n";
+ print " $_\n" for @categories;
+
+ print "Split this CF's categories into a hierarchical custom field (Y/n)? ";
+ my $dothis = <>;
+ next if $dothis =~ /n/i;
+
+ print "Enter name of CF to create as category ('@{[$cf->Name]} category'): ";
+ my $newname = <>;
+ chomp $newname;
+ $newname = $cf->Name . " category" unless length $newname;
+
+ # bump the CF's sort oder up by one
+ $cf->SetSortOrder( ($cf->SortOrder || 0) + 1 );
+
+ # ..and add a new CF before it
+ my $new = RT::CustomField->new( $RT::SystemUser );
+ my ($id, $msg) = $new->Create(
+ Name => $newname,
+ Type => 'Select',
+ MaxValues => 1,
+ LookupType => $cf->LookupType,
+ SortOrder => $cf->SortOrder - 1,
+ );
+ die "Can't create custom field '$newname': $msg" unless $id;
+
+ # Set the CF to be based on what we just made
+ $cf->SetBasedOn( $new->Id );
+
+ # Apply it to all of the same things
+ {
+ my $ocfs = RT::ObjectCustomFields->new( $RT::SystemUser );
+ $ocfs->LimitToCustomField( $cf->Id );
+ while (my $ocf = $ocfs->Next) {
+ my $newocf = RT::ObjectCustomField->new( $RT::SystemUser );
+ ($id, $msg) = $newocf->Create(
+ SortOrder => $ocf->SortOrder,
+ CustomField => $new->Id,
+ ObjectId => $ocf->ObjectId,
+ );
+ die "Can't create ObjectCustomField: $msg" unless $id;
+ }
+ }
+
+ # Copy over all of the rights
+ {
+ my $acl = RT::ACL->new( $RT::SystemUser );
+ $acl->LimitToObject( $cf );
+ while (my $ace = $acl->Next) {
+ my $newace = RT::ACE->new( $RT::SystemUser );
+ ($id, $msg) = $newace->Create(
+ PrincipalId => $ace->PrincipalId,
+ PrincipalType => $ace->PrincipalType,
+ RightName => $ace->RightName,
+ Object => $new,
+ );
+ die "Can't assign rights: $msg" unless $id;
+ }
+ }
+
+ # Add values for all of the categories
+ for my $i (0..$#categories) {
+ ($id, $msg) = $new->AddValue(
+ Name => $categories[$i],
+ SortOrder => $i + 1,
+ );
+ die "Can't create custom field value: $msg" unless $id;
+ }
+
+ # Grovel through all ObjectCustomFieldValues, and add the
+ # appropriate category
+ {
+ my $ocfvs = RT::ObjectCustomFieldValues->new( $RT::SystemUser );
+ $ocfvs->LimitToCustomField( $cf->Id );
+ while (my $ocfv = $ocfvs->Next) {
+ next unless exists $mapping{$ocfv->Content};
+ my $newocfv = RT::ObjectCustomFieldValue->new( $RT::SystemUser );
+ ($id, $msg) = $newocfv->Create(
+ CustomField => $new->Id,
+ ObjectType => $ocfv->ObjectType,
+ ObjectId => $ocfv->ObjectId,
+ Content => $mapping{$ocfv->Content},
+ );
+ }
+ }
+}
+
+$RT::Handle->Commit;
+print "No custom fields with categories found\n" unless $seen;
diff --git a/rt/etc/upgrade/split-out-cf-categories.in b/rt/etc/upgrade/split-out-cf-categories.in
new file mode 100644
index 0000000..f34a1b6
--- /dev/null
+++ b/rt/etc/upgrade/split-out-cf-categories.in
@@ -0,0 +1,171 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC
+# <jesse@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;
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+use RT;
+RT::LoadConfig();
+RT->Config->Set('LogToScreen' => 'debug');
+RT::Init();
+
+$| = 1;
+
+$RT::Handle->BeginTransaction();
+
+use RT::CustomFields;
+my $CFs = RT::CustomFields->new( $RT::SystemUser );
+$CFs->UnLimit;
+$CFs->Limit( FIELD => 'Type', VALUE => 'Select' );
+
+my $seen;
+while (my $cf = $CFs->Next ) {
+ next if $cf->BasedOnObj->Id;
+ my @categories;
+ my %mapping;
+ my $values = $cf->Values;
+ while (my $value = $values->Next) {
+ next unless defined $value->Category and length $value->Category;
+ push @categories, $value->Category unless grep {$_ eq $value->Category} @categories;
+ $mapping{$value->Name} = $value->Category;
+ }
+ next unless @categories;
+
+ $seen++;
+ print "Found CF '@{[$cf->Name]}' with categories:\n";
+ print " $_\n" for @categories;
+
+ print "Split this CF's categories into a hierarchical custom field (Y/n)? ";
+ my $dothis = <>;
+ next if $dothis =~ /n/i;
+
+ print "Enter name of CF to create as category ('@{[$cf->Name]} category'): ";
+ my $newname = <>;
+ chomp $newname;
+ $newname = $cf->Name . " category" unless length $newname;
+
+ # bump the CF's sort oder up by one
+ $cf->SetSortOrder( ($cf->SortOrder || 0) + 1 );
+
+ # ..and add a new CF before it
+ my $new = RT::CustomField->new( $RT::SystemUser );
+ my ($id, $msg) = $new->Create(
+ Name => $newname,
+ Type => 'Select',
+ MaxValues => 1,
+ LookupType => $cf->LookupType,
+ SortOrder => $cf->SortOrder - 1,
+ );
+ die "Can't create custom field '$newname': $msg" unless $id;
+
+ # Set the CF to be based on what we just made
+ $cf->SetBasedOn( $new->Id );
+
+ # Apply it to all of the same things
+ {
+ my $ocfs = RT::ObjectCustomFields->new( $RT::SystemUser );
+ $ocfs->LimitToCustomField( $cf->Id );
+ while (my $ocf = $ocfs->Next) {
+ my $newocf = RT::ObjectCustomField->new( $RT::SystemUser );
+ ($id, $msg) = $newocf->Create(
+ SortOrder => $ocf->SortOrder,
+ CustomField => $new->Id,
+ ObjectId => $ocf->ObjectId,
+ );
+ die "Can't create ObjectCustomField: $msg" unless $id;
+ }
+ }
+
+ # Copy over all of the rights
+ {
+ my $acl = RT::ACL->new( $RT::SystemUser );
+ $acl->LimitToObject( $cf );
+ while (my $ace = $acl->Next) {
+ my $newace = RT::ACE->new( $RT::SystemUser );
+ ($id, $msg) = $newace->Create(
+ PrincipalId => $ace->PrincipalId,
+ PrincipalType => $ace->PrincipalType,
+ RightName => $ace->RightName,
+ Object => $new,
+ );
+ die "Can't assign rights: $msg" unless $id;
+ }
+ }
+
+ # Add values for all of the categories
+ for my $i (0..$#categories) {
+ ($id, $msg) = $new->AddValue(
+ Name => $categories[$i],
+ SortOrder => $i + 1,
+ );
+ die "Can't create custom field value: $msg" unless $id;
+ }
+
+ # Grovel through all ObjectCustomFieldValues, and add the
+ # appropriate category
+ {
+ my $ocfvs = RT::ObjectCustomFieldValues->new( $RT::SystemUser );
+ $ocfvs->LimitToCustomField( $cf->Id );
+ while (my $ocfv = $ocfvs->Next) {
+ next unless exists $mapping{$ocfv->Content};
+ my $newocfv = RT::ObjectCustomFieldValue->new( $RT::SystemUser );
+ ($id, $msg) = $newocfv->Create(
+ CustomField => $new->Id,
+ ObjectType => $ocfv->ObjectType,
+ ObjectId => $ocfv->ObjectId,
+ Content => $mapping{$ocfv->Content},
+ );
+ }
+ }
+}
+
+$RT::Handle->Commit;
+print "No custom fields with categories found\n" unless $seen; \ No newline at end of file
diff --git a/rt/etc/upgrade/upgrade-mysql-schema.pl b/rt/etc/upgrade/upgrade-mysql-schema.pl
new file mode 100755
index 0000000..bc59c97
--- /dev/null
+++ b/rt/etc/upgrade/upgrade-mysql-schema.pl
@@ -0,0 +1,390 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use DBI;
+use DBD::mysql 4.002;
+
+unless (@ARGV) {
+ print STDERR "usage: $0 db_name[:server_name] db_user db_password\n";
+ exit 1;
+}
+
+# pretty correct support of charsets has been introduced in mysql 4.1
+# as RT doesn't use it may result in issues:
+# 1) data corruptions when default charset of mysql server has data restrictions like utf8
+# 2) wrong ordering (collations)
+
+# we have to define correct types for all columns. RT uses UTF-8, ascii and binary.
+# * ascii is subset of many mysql's charsets except may be one or two rare where some ascii
+# characters replaced with local
+# * for many charsets mysql allows us to store any octets sequences even when those are
+# invalid for this particula set, for example we can store UTF-8 data in latin1
+# column and fetch it as UTF-8, however sorting will be wrong
+
+# here is tricky algorithm to change column to desired charset:
+# * text to binary convertion is pretty straight forward except that text types
+# have length definitions in terms of characters and in some cases we must
+# use longer binary types to satisfy space requirements
+# * binary to text is much easier as we know that there is ascii or UTF-8 then
+# we just make convertion, also 32 chars are long enough to store 32 bytes, so
+# length changes is not required
+# * text to text convertion is trickier. no matter what is the current character set
+# of the column we know that there is either ascii or UTF-8, so we can not use
+# direct convertion, instead we do text to binary plus binary to text convertion
+# instead
+# * as well we add charset definition for all tables and for the DB as well,
+# so all new columns by default will be in UTF-8 charset
+
+my @tables = qw(
+ ACL
+ Attachments
+ Attributes
+ CustomFields
+ CustomFieldValues
+ GroupMembers
+ Groups
+ Links
+ ObjectCustomFields
+ ObjectCustomFieldValues
+ Principals
+ Queues
+ ScripActions
+ ScripConditions
+ Scrips
+ sessions
+ Templates
+ Tickets
+ Transactions
+ Users
+);
+
+my %charset = (
+ ACL => {
+ RightName => 'ascii',
+ ObjectType => 'ascii',
+ PrincipalType => 'ascii',
+ },
+ Attachments => {
+ MessageId => 'ascii',
+ Subject => 'utf8',
+ Filename => 'utf8',
+ ContentType => 'ascii',
+ ContentEncoding => 'ascii',
+ Content => 'binary',
+ Headers => 'utf8',
+ },
+ Attributes => {
+ Name => 'utf8',
+ Description => 'utf8',
+ Content => 'binary',
+ ContentType => 'ascii',
+ ObjectType => 'ascii',
+ },
+ CustomFields => {
+ Name => 'utf8',
+ Type => 'ascii',
+ Pattern => 'utf8',
+ Description => 'utf8',
+ LookupType => 'ascii',
+ },
+ CustomFieldValues => {
+ Name => 'utf8',
+ Description => 'utf8',
+ },
+ Groups => {
+ Name => 'utf8',
+ Description => 'utf8',
+ Domain => 'ascii',
+ Type => 'ascii',
+ },
+ Links => {
+ Base => 'ascii',
+ Target => 'ascii',
+ Type => 'ascii',
+ },
+ ObjectCustomFieldValues => {
+ ObjectType => 'ascii',
+ Content => 'utf8',
+ LargeContent => 'binary',
+ ContentType => 'ascii',
+ ContentEncoding => 'ascii',
+ },
+ Principals => {
+ PrincipalType => 'ascii',
+ },
+ Queues => {
+ Name => 'utf8',
+ Description => 'utf8',
+ CorrespondAddress => 'ascii',
+ CommentAddress => 'ascii',
+ },
+ ScripActions => {
+ Name => 'utf8',
+ Description => 'utf8',
+ ExecModule => 'ascii',
+ Argument => 'binary',
+ },
+ ScripConditions => {
+ Name => 'utf8',
+ Description => 'utf8',
+ ExecModule => 'ascii',
+ Argument => 'binary',
+ ApplicableTransTypes => 'ascii',
+ },
+ Scrips => {
+ Description => 'utf8',
+ ConditionRules => 'utf8',
+ ActionRules => 'utf8',
+ CustomIsApplicableCode => 'utf8',
+ CustomPrepareCode => 'utf8',
+ CustomCommitCode => 'utf8',
+ Stage => 'ascii',
+ },
+ sessions => {
+ id => 'binary', # ascii?
+ a_session => 'binary',
+ },
+ Templates => {
+ Name => 'utf8',
+ Description => 'utf8',
+ Type => 'ascii',
+ Language => 'ascii',
+ Content => 'utf8',
+ },
+ Tickets => {
+ Type => 'ascii',
+ Subject => 'utf8',
+ Status => 'ascii',
+ },
+ Transactions => {
+ ObjectType => 'ascii',
+ Type => 'ascii',
+ Field => 'ascii',
+ OldValue => 'utf8',
+ NewValue => 'utf8',
+ ReferenceType => 'ascii',
+ Data => 'utf8',
+ },
+ Users => {
+ Name => 'utf8',
+ Password => 'binary',
+ Comments => 'utf8',
+ Signature => 'utf8',
+ EmailAddress => 'ascii',
+ FreeformContactInfo => 'utf8',
+ Organization => 'utf8',
+ RealName => 'utf8',
+ NickName => 'utf8',
+ Lang => 'ascii',
+ EmailEncoding => 'ascii',
+ WebEncoding => 'ascii',
+ ExternalContactInfoId => 'utf8',
+ ContactInfoSystem => 'utf8',
+ ExternalAuthId => 'utf8',
+ AuthSystem => 'utf8',
+ Gecos => 'utf8',
+ HomePhone => 'utf8',
+ WorkPhone => 'utf8',
+ MobilePhone => 'utf8',
+ PagerPhone => 'utf8',
+ Address1 => 'utf8',
+ Address2 => 'utf8',
+ City => 'utf8',
+ State => 'utf8',
+ Zip => 'utf8',
+ Country => 'utf8',
+ Timezone => 'ascii',
+ PGPKey => 'binary',
+ },
+);
+
+my %max_type_length = (
+ char => int 1<<8,
+ varchar => int 1<<8,
+ tinytext => int 1<<8,
+ mediumtext => int 1<<16,
+ text => int 1<<24,
+ longtext => int 1<<32,
+);
+
+my @sql_commands;
+
+my ($db_datasource, $db_user, $db_pass) = (shift, shift, shift);
+my $dbh = DBI->connect("dbi:mysql:$db_datasource", $db_user, $db_pass, { RaiseError => 1 });
+my $db_name = $db_datasource;
+$db_name =~ s/:.*$//;
+
+my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
+($version) = $version =~ /^(\d+\.\d+)/;
+
+push @sql_commands, qq{ALTER DATABASE $db_name DEFAULT CHARACTER SET utf8};
+convert_table($_) foreach @tables;
+
+print join "\n", map(/;$/? $_ : "$_;", @sql_commands), "";
+my $use_p = $db_pass ? " -p" : '';
+print STDERR <<ENDREMINDER;
+-- ** NOTICE: No database changes have been made. **
+-- Please review the generated SQL, ensure you have a full backup of your database
+-- and apply it to your database using a command like:
+-- mysql -u ${db_user}${use_p} $db_name < queries.sql";
+ENDREMINDER
+exit 0;
+
+my %alter_aggregator;
+sub convert_table {
+ my $table = shift;
+ @alter_aggregator{'char_to_binary','binary_to_char'} = (['DEFAULT CHARACTER SET utf8'],[]);
+
+ my $sth = $dbh->column_info( undef, $db_name, $table, undef );
+ $sth->execute;
+ while ( my $info = $sth->fetchrow_hashref ) {
+ convert_column(%$info);
+ }
+ for my $conversiontype (qw(char_to_binary binary_to_char)) {
+ next unless @{$alter_aggregator{$conversiontype}};
+ push @sql_commands, qq{ALTER TABLE $table\n }.
+ join(",\n ",@{$alter_aggregator{$conversiontype}});
+ }
+}
+
+sub convert_column {
+ my %info = @_;
+ my $table = $info{'TABLE_NAME'};
+ my $column = $info{'COLUMN_NAME'};
+ my $type = $info{'TYPE_NAME'};
+ return unless $type =~ /(CHAR|TEXT|BLOB|BINARY)$/i;
+
+ my $required_charset = $charset{$table}{$column};
+ unless ( $required_charset ) {
+ print STDERR join(".", @info{'TABLE_SCHEMA', 'TABLE_NAME', 'COLUMN_NAME'})
+ ." has type $type however mapping is missing.\n";
+ return;
+ }
+
+ my $collation = column_info($table, $column)->{'collation'};
+ # mysql 4.1 returns literal NULL instead of undef
+ my $current_charset = $collation && $collation ne 'NULL'? (split /_/, $collation)[0]: 'binary';
+ return if $required_charset eq $current_charset;
+
+ if ( $required_charset eq 'binary' ) {
+ char_to_binary(%info);
+ }
+ elsif ( $current_charset eq 'binary' ) {
+ binary_to_char( $required_charset, %info);
+ } else {
+ char_to_char( $required_charset, %info);
+ }
+}
+
+sub char_to_binary {
+ my %info = @_;
+
+ my $table = $info{'TABLE_NAME'};
+ my $column = $info{'COLUMN_NAME'};
+ my $new_type = calc_suitable_binary_type(%info);
+ push @{$alter_aggregator{char_to_binary}},
+ "MODIFY $column $new_type ".build_column_definition(%info);
+
+}
+
+sub binary_to_char {
+ my ($charset, %info) = @_;
+
+ my $table = $info{'TABLE_NAME'};
+ my $column = $info{'COLUMN_NAME'};
+ my $new_type = lc $info{'TYPE_NAME'};
+ if ( $new_type =~ /binary/ ) {
+ $new_type =~ s/binary/char/;
+ $new_type .= '('. $info{'COLUMN_SIZE'} .')';
+ } else {
+ $new_type =~ s/blob/text/;
+ }
+
+ push @{$alter_aggregator{binary_to_char}},
+ "MODIFY $column ". uc($new_type) ." CHARACTER SET ". $charset
+ ." ". build_column_definition(%info);
+}
+
+sub char_to_char {
+ my ($charset, %info) = @_;
+
+ my $table = $info{'TABLE_NAME'};
+ my $column = $info{'COLUMN_NAME'};
+ my $new_type = $info{'mysql_type_name'};
+
+ char_to_binary(%info);
+ push @{$alter_aggregator{binary_to_char}},
+ "MODIFY $column ". uc($new_type)." CHARACTER SET ". $charset
+ ." ". build_column_definition(%info);
+}
+
+sub calc_suitable_binary_type {
+ my %info = @_;
+ my $type = lc $info{'TYPE_NAME'};
+ return 'LONGBLOB' if $type eq 'longtext';
+
+ my $current_max_byte_length = column_byte_length(@info{qw(TABLE_NAME COLUMN_NAME)}) || 0;
+ if ( $max_type_length{ $type } > $current_max_byte_length ) {
+ if ( $type eq 'varchar' || $type eq 'char' ) {
+ my $new_type = $type;
+ $new_type =~ s/char/binary/;
+ $new_type .= $info{'COLUMN_SIZE'} >= $current_max_byte_length
+ ? '('. $info{'COLUMN_SIZE'} .')'
+ : '('. $current_max_byte_length .')';
+ return uc $new_type;
+ } else {
+ my $new_type = $type;
+ $new_type =~ s/text/blob/;
+ return uc $new_type;
+ }
+ } else {
+ my $new_type;
+ foreach ( sort { $max_type_length{$a} <=> $max_type_length{$b} } keys %max_type_length ) {
+ next if $max_type_length{ $_ } <= $current_max_byte_length;
+
+ $new_type = $_; last;
+ }
+ $new_type =~ s/text/blob/;
+ return uc $new_type;
+ }
+}
+
+sub build_column_definition {
+ my %info = @_;
+
+ my $res = '';
+ $res .= 'NOT ' unless $info{'NULLABLE'};
+ $res .= 'NULL';
+ my $default = column_info(@info{qw(TABLE_NAME COLUMN_NAME)})->{default};
+ if ( defined $default ) {
+ $res .= ' DEFAULT '. $dbh->quote($default);
+ } elsif ( $info{'NULLABLE'} ) {
+ $res .= ' DEFAULT NULL';
+ }
+ $res .= ' AUTO_INCREMENT' if $info{'mysql_is_auto_increment'};
+ return $res;
+}
+
+sub column_byte_length {
+ my ($table, $column) = @_;
+ if ( $version >= 5.0 ) {
+ my ($char, $octet) = @{ $dbh->selectrow_arrayref(
+ "SELECT CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH FROM information_schema.COLUMNS WHERE"
+ ." TABLE_SCHEMA = ". $dbh->quote($db_name)
+ ." AND TABLE_NAME = ". $dbh->quote($table)
+ ." AND COLUMN_NAME = ". $dbh->quote($column)
+ ) };
+ return $octet if $octet == $char;
+ }
+ return $dbh->selectrow_arrayref("SELECT MAX(LENGTH(". $dbh->quote_identifier($column) .")) FROM $table")->[0];
+}
+
+sub column_info {
+ my ($table, $column) = @_;
+ # XXX: DBD::mysql doesn't provide this info, may be will do in 4.0007 if I'll write a patch
+ local $dbh->{FetchHashKeyName} = 'NAME_lc';
+ return $dbh->selectrow_hashref("SHOW FULL COLUMNS FROM $table LIKE " . $dbh->quote($column));
+}
+