=== modified file Bugzilla/Constants.pm --- Bugzilla/Constants.pm 2010-03-09 17:58:48 +0000 +++ Bugzilla/Constants.pm 2010-03-31 22:38:57 +0000 @@ -452,6 +452,13 @@ version => '1.19', }, name => 'Oracle'}, + 'mssql' => {db => 'Bugzilla::DB::Mssql', db_version => '10.00.000', + dbd => { + package => 'DBD-ODBC', + module => 'DBD::ODBC', + version => '1.23', + }, + name => 'Mssql'}, }; # True if we're on Win32. === added file Bugzilla/DB/Mssql.pm --- Bugzilla/DB/Mssql.pm 1970-01-01 00:00:00 +0000 +++ Bugzilla/DB/Mssql.pm 2010-04-01 16:44:48 +0000 @@ -1,0 +1,720 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# Michael Thomas + +=head1 NAME + +Bugzilla::DB::Mssql - Bugzilla database compatibility layer for MSSQL + +=head1 DESCRIPTION + +This module overrides methods of the Bugzilla::DB module with MSSQL specific +implementation. It is instantiated by the Bugzilla::DB module and should never +be used directly. + +For interface details see L and L. + +=cut + +package Bugzilla::DB::Mssql; + +use strict; + +use Bugzilla::Constants; +use Bugzilla::Install::Util qw(install_string); +use Bugzilla::Util; +use Bugzilla::Error; +use Bugzilla::DB::Schema::Mssql; + +use List::Util qw(max); + +my %locks; + +# This is how many comments of MAX_COMMENT_LENGTH we expect on a single bug. +# In reality, you could have a LOT more comments than this, because +# MAX_COMMENT_LENGTH is big. +use constant MAX_COMMENTS => 50; + +use constant EMPTY_STRING => ''; +use constant BLOB_TYPE => undef ; + +# This module extends the DB interface via inheritance +use base qw(Bugzilla::DB); + +sub new { + my ($class, $user, $pass, $host, $dbname, $port, $sock) = @_; + + # construct the DSN from the parameters we got + my $dsn = "dbi:ODBC:Driver={SQL Server Native Client 10.0};Server=$host;UID=$user;PWD=$pass;Database=$dbname;APP=Bugzilla;MARS_Connection=Yes;"; + my $attrs = { odbc_default_bind_type => '-9', LongReadLen => 5000000 }; + + my $self = $class->db_new($dsn, $user, $pass, $attrs); + + # Needed by TheSchwartz + $self->{private_bz_dsn} = \($dsn, $user, $pass, $attrs); + + # all class local variables stored in DBI derived class needs to have + # a prefix 'private_'. See DBI documentation. + $self->{private_bz_tables_locked} = ""; + + bless ($self, $class); + + return $self; +} + +sub bz_explain { + my ($self, $sql) = @_; + # effectly does nothing but allow SQL to display + return ''; +} + +sub bz_last_key { + my ($self) = @_; + + my ($last_insert_id) = $self->selectrow_array('SELECT @@IDENTITY'); + + return $last_insert_id; +} + +sub sql_string_until { + my ($self, $string, $substring) = @_; + return "SUBSTRING($string, 1, " . + $self->sql_position($substring, $string) . " - 1)"; +} + +sub sql_regexp { + my ($self, $expr, $pattern) = @_; + + $pattern = "'\\\\x00-\\\\x1f'" if ($pattern eq "'[[:cntrl:]]'"); + + return "dbo.REGEXP($expr,$pattern, 0) = 1"; +} + +sub sql_not_regexp { + my ($self, $expr, $pattern) = @_; + + $pattern = "'\\\\x00-\\\\x1f'" if ($pattern eq "'[[:cntrl:]]'"); + return "dbo.NOT_REGEXP($expr,$pattern, 0) = 1" +} + +sub sql_limit { + my ($self, $limit, $offset) = @_; + + if(defined $offset) { + return "/* LIMIT $limit $offset */"; + } + return "/* LIMIT $limit */"; +} + +sub sql_string_concat { + my ($self, @params) = @_; + + my @hack_for_hack; + foreach my $string (@params){ + push @hack_for_hack, "cast($string as nvarchar(max))"; + } + + return join(' + ', @hack_for_hack); +} + +# Currently Unsupportable, CONTAINS can not be used in GROUP BY +#sub sql_fulltext_search { +# my ($self, $column, $text) = @_; + + # quote the text for use in the MATCH AGAINST expression +# $text = $self->quote($text); + + # untaint the text, since it's safe to use now that we've quoted it +# trick_taint($text); + +# return "CASE WHEN CONTAINS($column, $text) THEN 1 ELSE 0 END"; +#} + +sub sql_istring { + my ($self, $string) = @_; + + return $string; +} + +sub sql_from_days { + my ($self, $days) = @_; + + return " dateadd(dd,0, ( $days )) "; +} + + +sub sql_to_days { + my ($self, $date) = @_; + + return " datediff(dd,0, $date ) "; +} + +sub sql_date_format { + my ($self, $date, $format) = @_; + + $format = trim($format); + $format = '%Y.%m.%d %H:%i:%s' unless $format; + + return " dbo.DATE_FORMAT($date,'$format') "; + + if ($format && $format eq '%Y%m%d'){ + return "REPLACE(cast(CAST($date as DATE) as NVARCHAR),'-','')"; + } + elsif ($format && $format eq '%Y-%m-%d'){ + return "REPLACE(cast(CAST($date as DATE) as NVARCHAR),'-','.')"; + } + elsif ($format && $format eq '%Y.%m.%d %H:%i'){ + return "REPLACE(CAST(CAST($date as DATE) as NVARCHAR)" + ." +' '+ LEFT(CAST(CAST($date as TIME(0)) as NVARCHAR),5),'-','.')"; + } + elsif ($format && $format eq '%Y.%m.%d %H:%i:%s'){ + return "REPLACE(CAST(CAST($date as DATE) as NVARCHAR)" + ." +' '+ CAST(CAST($date as TIME(0)) as NVARCHAR),'-','.')"; + } else { + return "CAST(CAST($date as DATE) as NVARCHAR) +' '+ CAST(CAST($date as TIME(0)) as NVARCHAR)"; + } +} + +sub sql_interval { + my ($self, $interval, $units) = @_; + + return "/*INTERVAL $interval $units*/"; +} + +sub sql_iposition { + my ($self, $fragment, $text) = @_; + return "dbo.INSTR($fragment, $text, 0)"; +} + +sub sql_position { + my ($self, $fragment, $text) = @_; + return "dbo.INSTR($fragment, $text, 1)"; +} + +sub sql_group_by { + my ($self, $needed_columns, $optional_columns) = @_; + + return ($optional_columns ? "GROUP BY $needed_columns, $optional_columns" : "GROUP BY $needed_columns"); +} + +sub sql_group_concat { + my ($self, $column, $separator, $sortorder) = @_; + + $column = trim($column); + + $separator = "'$separator'" if $separator !~ /^'.*'$/; + + $sortorder = 'NTRL' unless $sortorder =~ /ASC|DESC/; + $sortorder = "'$sortorder'" if $sortorder !~ /^'.*'$/; + + return "dbo.GROUP_CONCAT($column, $separator, $sortorder)"; +} + +sub LOCALTIMESTAMP { + my ($self, $arg) = @_; + $arg = '0' unless $arg; + return "GETDATE()"; +} + +sub MOD { + my ($self, $arg1, $arg2) = @_; + return "($arg1 \% $arg2)"; +} + +##################################################################### +# Database Setup +##################################################################### + +sub bz_setup_database { + my ($self) = @_; + + print "Regenerating CLR Functions for mssql..."; + + $self->do("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[FROM_DAYS]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) + DROP FUNCTION [dbo].[FROM_DAYS]"); + + $self->do("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[GROUP_CONCAT]') AND type = N'AF') + DROP AGGREGATE dbo.GROUP_CONCAT"); + + $self->do("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[INSTR]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) + DROP FUNCTION [dbo].[INSTR]"); + + $self->do("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[NOT_REGEXP]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) + DROP FUNCTION [dbo].[NOT_REGEXP]"); + + $self->do("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[REGEXP]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) + DROP FUNCTION [dbo].[REGEXP]"); + + $self->do("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TO_DAYS]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) + DROP FUNCTION [dbo].[TO_DAYS]"); + + $self->do("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[DATE_FORMAT]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT')) + DROP FUNCTION [dbo].[DATE_FORMAT]"); + + $self->do("IF EXISTS (SELECT * FROM sys.assemblies asms WHERE asms.name = N'Bugzilla.MSSQL' and is_user_defined = 1) + DROP ASSEMBLY [Bugzilla.MSSQL]"); + + $self->do(q| + CREATE ASSEMBLY [Bugzilla.MSSQL] + AUTHORIZATION [dbo] + FROM 0x4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C010300D9DEB34B0000000000000000E00002210B010800003600000006000000000000AE55000000200000006000000000400000200000000200000400000000000000040000000000000000A0000000020000000000000300408500001000001000000000100000100000000000001000000000000000000000005455000057000000006000000803000000000000000000000000000000000000008000000C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E74657874000000B4350000002000000036000000020000000000000000000000000000200000602E7273726300000008030000006000000004000000380000000000000000000000000000400000402E72656C6F6300000C0000000080000000020000003C0000000000000000000000000000400000420000000000000000000000000000000090550000000000004800000002000500D83500007C1F00000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000133005004B0000000100001100036F1100000A166AFE0116FE010B072D15178D200000010C08160F00281200000AA2080A2B220F00281200000A178D210000010D091603166A6F1300000A9D096F1400000A0A2B00062A4E0003027420000001731500000A81090000012A0013300300B900000002000011000F00281600000A2D0C0F01281600000A16FE012B0116130411042D0C007E1700000A0D388E0000000F00281200000A0A0F01281200000A0B037201000070281800000A281900000A281A00000A16FE01130411042D09007E1700000A0D2B570F02281B00000A281C00000A7E1D00000A281E00000A281A00000A16FE01130411042D0D00060717281F00000A0C002B0A000607282000000A0C000816FE01130411042D09007E1D00000A0D2B09007E1700000A0D2B00092A000000133003009500000003000011000F00281600000A2D0C0F01281600000A16FE012B01160D092D09007E1700000A0C2B6F0F00281200000A0A0F01281200000A0B037201000070281800000A281900000A281A00000A16FE010D092D09007E1700000A0C2B3A06281800000A07281800000A0428030000067E1D00000A281E00000A281A00000A16FE010D092D09007E1700000A0C2B09007E1D00000A0C2B00082A000000133003008E00000004000011000F00281600000A2D0C0F01281600000A16FE012B0116130411042D0A0016282100000A0D2B650F00281200000A0A0F01281200000A0B0417281C00000A281E00000A281A00000A16FE01130411042D0D000706186F2200000A0C002B0B000706196F2200000A0C000815FE0116FE01130411042D0A0016282100000A0D2B0C00081758282100000A0D2B00092A000013300100100000000500001100282300000A282400000A0A2B00062A133001000B000000050000110028060000060A2B00062A00133002001A000000060000110028060000067203000070281800000A280B0000060A2B00062A0000133004004900000007000011000F00282500000A0A1201206C0700001717282600000A001200282700000A1201282700000A590C120308282800000A001203282900000A282A00000A282100000A13042B0011042A000000133004005000000008000011000F00282B00000A16FE010C082D09007E2C00000A0B2B361200206C0700001717282600000A0012000F00282D00000A6C282E00000A2612000F00282D00000A6C282E00000A282400000A0B2B00072A133005002807000009000011000F00282F00000A16FE01130511052D0C283000000A130438080700000F01281200000A0A0F00282500000A0B1201283100000A8C280000016F3200000A0C0672150000706F3300000A16FE01130511052D26000672150000701201283100000A8C280000016F3200000A16196F3400000A6F3500000A0A0006721B0000706F3300000A16FE01130511052D1F0006721B0000701201283600000A8C040000026F3200000A6F3500000A0A000672210000706F3300000A16FE01130511052D1E000672210000701201283600000A13061206283700000A6F3500000A0A000672270000706F3300000A16FE01130511052D2E000672270000701201283800000A13061206283700000A07282400000A280C000006283900000A6F3500000A0A0006722D0000706F3300000A16FE01130511052D260006722D0000701201283800000A13061206283700000A181F306F3A00000A6F3500000A0A000672330000706F3300000A16FE01130511052D1E000672330000701201283800000A13061206283700000A6F3500000A0A000672390000706F3300000A16FE01130511052D26000672390000701201283B00000A13061206283700000A1C1F306F3C00000A6F3500000A0A0006723F0000706F3300000A16FE01130511052D260006723F0000701201283D00000A13061206283700000A181F306F3A00000A6F3500000A0A000672450000706F3300000A16FE01130511052D26000672450000701201283D00000A13061206283700000A181F306F3A00000A6F3500000A0A0006724B0000706F3300000A16FE01130511052D260006724B0000701201283D00000A13061206283700000A181F306F3A00000A6F3500000A0A000672510000706F3300000A16FE01130511052D26000672510000701201283E00000A13061206283700000A181F306F3A00000A6F3500000A0A000672570000706F3300000A16FE01130511052D26000672570000701201283F00000A13061206283700000A191F306F3A00000A6F3500000A0A0006725D0000706F3300000A16FE01130511052D260006725D0000701201283D00000A13061206283700000A181F306F3A00000A6F3500000A0A000672630000706F3300000A16FE01130511052D1E000672630000701201283D00000A13061206283700000A6F3500000A0A000672690000706F3300000A16FE01130511052D1F000672690000701201283600000A8C050000026F3200000A6F3500000A0A0006726F0000706F3300000A16FE01130511052D260006726F0000701201283600000A13061206283700000A181F306F3A00000A6F3500000A0A000672750000706F3300000A16FE01130511052D2E00727B0000700D1201283D00000A1F0CFE04130511052D080072810000700D00067275000070096F3500000A0A000672870000706F3300000A16FE01130511052D24000672870000701201284000000A13071207FE16260000016F3200000A6F3500000A0A0006728D0000706F3300000A16FE01130511052D26000672930000701201284100000A13061206283700000A181F306F3A00000A6F3500000A0A000672930000706F3300000A16FE01130511052D26000672930000701201284100000A13061206283700000A181F306F3A00000A6F3500000A0A000672990000706F3300000A16FE01130511052D1A000672990000701201284200000A6F3200000A6F3500000A0A0006729F0000706F3300000A16FE01130511052D220006729F00007007282400000A280D00000613061206283700000A6F3500000A0A000672A50000706F3300000A16FE01130511052D22000672A500007007282400000A280D00000613061206283700000A6F3500000A0A000672AB0000706F3300000A16FE01130511052D22000672AB00007007282400000A280D00000613061206283700000A6F3500000A0A000672B10000706F3300000A16FE01130511052D22000672B100007007282400000A280D00000613061206283700000A6F3500000A0A000672B70000706F3300000A16FE01130511052D1F000672B70000701201283100000A8C280000016F3200000A6F3500000A0A000672BD0000706F3300000A16FE01130511052D1F000672BD0000701201283100000A8C030000026F3200000A6F3500000A0A000672C30000706F3300000A16FE01130511052D1E000672C30000701201284300000A13061206283700000A6F3500000A0A000672C90000706F3300000A16FE01130511052D1E000672C90000701201284300000A13061206283700000A6F3500000A0A000672CF0000706F3300000A16FE01130511052D1E000672CF0000701201284300000A13061206283700000A6F3500000A0A000672D50000706F3300000A16FE01130511052D25000672D50000701201284300000A13061206283700000A18186F3400000A6F3500000A0A000672DB0000706F3300000A16FE01130511052D13000672DB00007072E10000706F3500000A0A00066F3200000A281800000A734400000A13042B0011042A13300200D00000000A000011000F00282F00000A16FE010D092D07140C38B80000000F00282500000A0A1200283800000A13041204283700000A0B0772E50000706F4500000A16FE010D092D27000772E50000706F4600000A2C0A1200283800000A17330772E90000702B0572EF0000700C2B660772F50000706F4500000A16FE010D092D1D000772E50000706F4600000A2D0772F90000702B0572EF0000700C2B370772FF0000706F4500000A16FE010D092D1D000772E50000706F4600000A2D0772030100702B0572EF0000700C2B0872EF0000700C2B00082A133004005A0000000B000011000F00282F00000A16FE01130611062D051613052B410F00282500000A0A284700000A6F4800000A0B076F4900000A0C076F4A00000A6F4B00000A0D076F4A00000A6F4C00000A130408060911046F4D00000A13052B0011052A1E02284E00000A2A6E0002735200000A7D2300000402147D2400000402147D250000042A000013300200140100000C000011000F01281600000A16FE010A062D0538FF0000000F02281600000A16FE010A062D0D0F027201000070281500000A00027B230000046F5300000A16FE0116FE010A063AB900000000027B230000040F02281200000A6F5400000A260F03281600000A2D4B0F03281200000A6F5500000A7209010070285600000A2C330F03281200000A6F5500000A7211010070285600000A2C1B0F03281200000A6F5500000A721B010070285600000A16FE012B01170A062D18000F03281200000A7225010070283900000A735700000A7A0F03281600000A16FE010A062D0D0F03721B010070281500000A00027B230000040F03281200000A6F5500000A6F5400000A2600027B230000040F01281200000A6F5400000A262A6A00027B230000040F017B230000046F5800000A6F5900000A002A00133003007B0000000D00001100027B25000004721B010070285600000A16FE010B072D11027B2300000473160000066F5A00000A00027B250000047211010070285B00000A16FE010B072D0C027B230000046F5C00000A00027B24000004027B23000004D020000001285D00000A6F5E00000A740200001B285F00000A731500000A0A2B00062A00133003007A0000000E00001100036F6000000A0A02735200000A7D23000004160B2B5500027B240000042D0E027B2500000414FE0116FE012B01170C082D200002036F6100000A7D2400000402036F6100000A7D250000040717580B002B1400027B23000004036F6100000A6F5400000A2600000717580B07061759FE0216FE010C082D9E2A00001B3002005D0000000F0000110003027B230000046F5300000A6F6200000A0000027B230000046F6300000A0B2B16076F6400000A74200000010A0003066F6500000A0000076F6600000A0C082DE0DE1707753A0000010D0914FE010C082D07096F6700000A00DC002A00000001100000020020002444001700000000A602284E00000A000002037D280000040202731B0000067D260000040202731B0000067D27000004002A2E021628150000060000002A000013300300D30000000E00001100027B26000004036F1C00000600027B27000004046F1C0000060000027B260000046F1D00000617FE01027B270000046F1D00000617FE015F16FE010C082D2000027B260000046F1E000006027B270000046F1E000006286800000A0A002B1E00027B260000046F1F000006027B270000046F1F000006286900000A0A000616FE010C082D0500060B2B4600027B260000046F2000000600027B270000046F20000006000000027B260000046F1D00000616FE01027B270000046F1D00000616FE015F16FE010C083A4EFFFFFF160B2B00072A0013300200750000001000001100020B071F43594502000000460000004B000000071F49594505000000170000003E0000003E0000002400000036000000071F5659450300000006000000290000000A0000002B27170A2B271B0A2B231F0A0A2B1E1F320A2B191F640A2B1420F40100000A2B0C20E80300000A2B04160A2B00062A000000133002003E0000001100001100027B26000004036F1C00000600027B260000046F1D00000617FE0116FE010B072D1400027B260000046F1E000006286A00000A0A2B0500160A2B00062A000013300300180000001200001100020374200000010474200000016F0100000A0A2B00062A4602284E00000A000002037D34000004002A000013300300480000000C000011000314FE0116FE010A062D077E6B00000A100102037D3100000402036F6C00000A7D3200000402157D300000040216736D00000A7D2F00000402282100000600022820000006002A133001000C0000001300001100027B2D0000040A2B00062A13300200290000001400001100027B2D00000417FE0116FE010B072D0A00027B2F0000040A2B0C00725F01007073240000067A062A000000133001000C0000001500001100027B2E0000040A2B00062A133002006A0000000C0000110000027B3300000416FE0116FE010A062D110002167D2D00000402147D2E0000042B46027B33000004286E00000A16FE010A062D0A00022822000006002B2A027B33000004286F00000A16FE010A062D0A00022823000006002B0E00022821000006000000170A2B982A000013300300460000000C0000110002257B3000000417587D30000004027B30000004027B32000004FE040A062D0B0002167D33000004002B190002027B31000004027B300000046F7000000A7D33000004002A000013300500DE0000001600001100027B300000040A287100000A6F7200000A166F7000000A0B287100000A6F7300000A166F7000000A0C0002282100000600027B3300000407FE0116FE010D092D2F000002282100000600027B33000004286E00000A2D0B027B3300000408FE012B01170D092D022B0500170D2BD42B2600027B33000004286E00000A2D0B027B3300000408FE012B01170D092D022B060000170D2B9302027B3100000406027B3000000406596F3400000A7D2E000004027B2E000004027C2F000004287400000A16FE010D092D0B0002177D2D000004002B090002187D2D000004002A000013300500F40100001700001100027B300000040A027B340000047B28000004175F16FE0116FE010B160C20FFFFFF7F0D161304000716FE01130811083A5501000000027B3300000428180000061305110516FE0216FE01130811083A2F010000001613061105172E1111051F0A2E0B11051F64FE0116FE012B0116130811082D630002282100000600027B3300000428180000061307110711051F0A5AFE01110711051B5AFE016016FE01130811082D3000171306110709FE02130811082D1C00081107110559580C0228210000060011051F0A5B0D161304002B0400160B0000002B090002282100000600001106130811083A9400000000110509FE02130811083A8000000000081105580C091105FE0116FE01130811082D61001104175813041105130911091F0A30121109172E2411091B2E3111091F0A2E192B3B11091F322E2311091F642E0B110920F40100002E142B2411041AFE0216FE01130811082D02160B2B12110417FE0216FE01130811082D02160B2B00002B080011050D17130400002B0400160B0000002B0400160B00002B09000228210000060000027B33000004286F00000A130811082D022B09001713083878FEFFFF02027B3100000406027B3000000406596F3400000A7D2E0000040716FE01130811082D17000208287500000A7D2F00000402177D2D000004002B090002187D2D000004002A2E0203287600000A0000002A42534A4201000100000000000C00000076322E302E35303732370000000005006C000000C00B0000237E00002C0C0000700C000023537472696E6773000000009C180000E0010000235553007C1A00001000000023475549440000008C1A0000F004000023426C6F620000000000000002000001571FA20B0902000000FA013300160000010000003C0000000B0000003800000024000000230000000300000077000000250000001E00000017000000010000000300000003000000020000000200000001000000030000000500000000000A000100000000000600D500CE000600DC00CE000600E100CE000A001201F70006003E01230106005D014A0106006701CE00060071014A010A0092017D010A009C017D010A00BF017D010A00DC017D010A00EB017D01060017034A0106005B03510306006D03510306006004CE000A004105F7000600800561050600420630060600590630060600760630060600950630060600AE0630060600C70630060600E20630060600FD06300606001607300606004F072F0706006F072F070A009C07F70006004104CE000600C607CE000E003A081B080E0040081B0806005508CE0006006E08CE0006008908CE000600A008CE000600C608CE0006000C09CE000600BD09AC090600EB09D60906000A0AD6090600200AD6090600460AD60906008D0ACE000A00A30AF7000A00C40AF7000600CB0A61050600E10A61050600100BCE0006002A0B4A010600470BCE0006004C0BCE000600700BCE000600900B4A010600BF0BCE000600000CD60906005E0CCE000000000001000000000001000100010010001D0000000500010001000321000032000000090001000F000321000040000000090009000F000321000051000000090016000F0009211000610000000D0023000F00010010006E006E00050026001500030100007E000000090029001B00030010008800000005002D001B000100100095006E001D003500240001010000AE006E0009003500250006065402680056805C026B00568063026B0056806A026B00568072026B0056807C026B00568085026B0056808C026B0006065402680056809502920056809902920056809D0292005680A10292005680A50292005680A90292005680AD0292005680B10292005680B50292005680B90292005680BD0292005680C10292000606540268005680C502B4005680CD02B4005680D602B4005680DC02B4005680A502B4005680E202B4005680E702B4005680EC02B4005680F302B4005680FD02B40056800503B40056800E03B40001002103B8000100B502BC0001002C03BC0001008003E00001008903E00001009203E40006065402680056802F040C01568037040C01568041040C01010048040C0101005304BC0001006804100101007804680001007D04BC0001008504680001008A04140101009304170106065402680056802305E40056802805E40056803505E4005020000000009600A50110000100A720000000009600B70119000300BC20000000009600CA01210005008421000000009600D101210008002822000000009600E5012C000B00C422000000009600F70137000E00E022000000009600FB0137000E00F8220000000096000A023C000E002023000000009600170241000E0078230000000096001F0248000F00D42300000000960029024F001000082B000000009100350258001200E42B00000000910043025E0013004A2C0000000086184E0264001400522C000000008600310364001400702C0000000086003603BF001400902D0000000086004103C9001700AC2D0000000086004703CF001800342E00000000E6016803D4001800BC2E00000000E6017A03DA001900382F0000000086184E02E8001A00622F0000000086184E0264001B00702F00000000E101AA03EE001B005030000000009100EE03FC001D00D430000000008600FF0301011E00203100000000E1010A0406011F0044310000000086184E021B0121005831000000008600310321012200AC31000000008608A40426012300C431000000008608B2042B012300FC31000000008608C504300123001432000000008600D504640023008C32000000008100DF0464002300E032000000008100E80464002300CC33000000008100FC0464002300CC350000000086184E0221012300000001003D05000002005305000001005D05020002003D05000001008D05000002009505000003009D05000001008D05000002009505000003009D0500000100A80500000200AD0500000300B40500000100C20500000100C70500000100CC0500000200D40500000100CC0500000100CC0500000100DB0500000200E10500000300EB0500000100F50500000100FB0500000100FD0500000100AE0000000100FF05000002000706000001000F0600000100FF05000001001106000002001306000001001506000001002506000001002C060600110007000600070019000C00E603F4003100E603060191004E02640099004E026400A1004E022101A9004E022101B1004E022101B9004E022101C1004E022101C9004E022101D1004E022101D9004E022101E1004E022101E9004E027601F1004E026400F9004E0264005100B107D8014900BC0730015100CB07DC010101D407E10149004E0221014900DA07F2015900E507F6014900EB07FA014900F707000259000308090259000B08F2015900EB070F0259001608F6015900F707150211014D081E0211014D0827026100EB073E020101660844022901770855026900EB075B026900BC07FE0229014E02040329017F08D80131014E020B033101920810033901A80814036100DA07F2016900B00828036100BC072C032901B50830036900DA07F2015100BD083C002901D00840030900DE0830010101E70846030101F0084B030101FA085103290102092C034901DE083001290112092C0301011A095703010121095D03290129092C03010139095D03290142092C0329014B092C03290156092C03290164096303290172092C0329017D09300129018F092C0351004E0269030101980946030101A10946035101C40989035101F7098F035901130A95035901330A9B036901570AA10369016C0A400361017F0AA70309004E02640079014E02640081014E02C80391014E02EA0371004E0264007100EC0A2C037100F60AF1030101FA0A30010101020B2702A1014E0221017100220BFA037100360BFF0371002C0306040101F707270271003F0B6400B1015E0B0C047100220B15040101760B210479007B0B2C037900850B300181007A03760171009C0B3404C901AA0B3A0481007A032101C901B60BF201D101CB0B64008900E60349040101E60351048900D30B5C040101DF0BBC000101B1072C0389004E0276010901E50B7A040901ED0B7A040101F60B7F04D901110C8404D901210C3001D9013C0C30018900550C8A048900EB07990439004E022101E1014E026400080008006F0008000C007400080010007900080014007E0008001800830008001C008800080020008D0008002800740008002C007900080030007E0008003400830008003800880008003C008D00080040009600080044009B0008004800A00008004C00A50008005000AA0008005400AF0008005C007400080060007900080064007E0008006800830008006C008800080070008D00080074009600080078009B0008007C00A00008008000A50008008400AA0008008800AF000800A8006F000800AC0074000800B00079000800D8006F000800DC0074000800E0006F00200083007B0124001B0042012E007300C6042E002B00AC042E005300C0042E007B00CF042E005B00C0042E003300C0042E003B00C0042E004300C0042E004B00C004600083007400800083007400A00083007400A4001B004201C00083007400C3008302CF03C4001B004201E0008300740000018300740004011B004201200183006D0224011B004201400183006D02600183006D026301BB03740064011B00420184011B00420184021B004201A4021B005501E8012D0236024C0263026802190337036F037F03B403F60328042E043E045704620467046B047004760492049F040900010000007E003401000008053901000017053E0102001D00030002001E00050002001F00070007002E0003000700340005000A001E0404800000010000009E0EEC6E0000000000008D0700000200000000000000000000000100C500000000000200000000000000000000000100EB00000000000200000000000000000000000100CE000000000003000200040002000500020008000700090007000000003C4D6F64756C653E004275677A696C6C612E4D5353514C2E646C6C0055736572446566696E656446756E6374696F6E73004C6F6E674461796F665765656B0053686F72744D6F6E74684F6659656172004C6F6E674D6F6E74684F66596561720047524F55505F434F4E434154004E61747572616C436F6D706172657200546F6B656E5479706500537472696E67506172736572004E61747572616C436F6D7061726572457863657074696F6E004E61747572616C436F6D70617265724F7074696F6E73006D73636F726C69620053797374656D004F626A65637400456E756D0056616C7565547970650053797374656D2E44617461004D6963726F736F66742E53716C5365727665722E536572766572004942696E61727953657269616C697A650053797374656D2E436F6C6C656374696F6E732E47656E657269630049436F6D706172657260310053797374656D2E436F6C6C656374696F6E730049436F6D706172657200457863657074696F6E0049456E756D657261626C650053797374656D2E446174612E53716C54797065730053716C537472696E670053716C43686172730053706C6974546F41727261795461626C650046696C6C526F770053716C426F6F6C65616E00524547455850004E4F545F5245474558500053716C496E74333200494E5354520053716C4461746554696D65004E4F57004C4F43414C54494D455354414D500043555252454E545F4441544500544F5F444159530046524F4D5F4441595300444154455F464F524D41540047657444617465537566666978005765656B4F6659656172002E63746F720076616C75655F5F0053756E646179004D6F6E6461790054756573646179005765646E657364617900546875727364617900467269646179005361747572646179004A616E00466562004D617200417072004D6179004A756E004A756C0041756700536570004F6374004E6F7600446563004A616E75617279004665627275617279004D6172636800417072696C004A756E65004A756C79004175677573740053657074656D626572004F63746F626572004E6F76656D62657200446563656D6265720041727261794C6973740052656D656D626572656400536F727400496E697400416363756D756C617465004D65726765005465726D696E6174650053797374656D2E494F0042696E61727952656164657200526561640042696E617279577269746572005772697465006D50617273657231006D50617273657232006D4E61747572616C436F6D70617265724F7074696F6E730053797374656D2E436F6C6C656374696F6E732E47656E657269632E49436F6D70617265723C53797374656D2E537472696E673E2E436F6D7061726500436F6D7061726500526F6D616E4C657474657256616C756500526F6D616E56616C75650053797374656D2E436F6C6C656374696F6E732E49436F6D70617265722E436F6D70617265004E6F7468696E67004E756D65726963616C00537472696E67006D546F6B656E54797065006D537472696E6756616C756500446563696D616C006D4E756D65726963616C56616C7565006D496478006D536F75726365006D4C656E006D43757243686172006D4E61747572616C436F6D7061726572006765745F546F6B656E54797065006765745F4E756D65726963616C56616C7565006765745F537472696E6756616C7565004E657874546F6B656E004E657874436861720050617273654E756D65726963616C56616C7565005061727365537472696E67004E756D65726963616C56616C756500537472696E6756616C7565004E6F6E6500526F6D616E4E756D626572730044656661756C74007374720053716C46616365744174747269627574650064656C696D6974657200726F770053797374656D2E52756E74696D652E496E7465726F705365727669636573004F7574417474726962757465007375626A656374007061747465726E0069676E6F7265636173650066696E6400696E74686973004361736553656E736974697665006461746500646179730053716C4461746500666F726D61740056616C756500536570657261746F7200536F72744F726465720047726F75700072007700737472696E673100737472696E6732006300780079006E61747572616C436F6D706172657200736F75726365006D73670053797374656D2E5265666C656374696F6E00417373656D626C795469746C6541747472696275746500417373656D626C794465736372697074696F6E41747472696275746500417373656D626C79436F6E66696775726174696F6E41747472696275746500417373656D626C79436F6D70616E7941747472696275746500417373656D626C7950726F6475637441747472696275746500417373656D626C79436F7079726967687441747472696275746500417373656D626C7954726164656D61726B41747472696275746500417373656D626C7943756C7475726541747472696275746500417373656D626C7956657273696F6E4174747269627574650053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300436F6D70696C6174696F6E52656C61786174696F6E734174747269627574650052756E74696D65436F6D7061746962696C697479417474726962757465004275677A696C6C612E4D5353514C0053716C46756E6374696F6E417474726962757465006765745F4C656E677468006765745F56616C75650043686172006765745F4974656D0053706C6974006765745F49734E756C6C0046616C7365006F705F496D706C69636974006F705F457175616C697479006F705F54727565006765745F49735472756500547275650053797374656D2E546578742E526567756C617245787072657373696F6E730052656765780052656765784F7074696F6E730049734D6174636800537472696E67436F6D70617269736F6E00496E6465784F66004461746554696D65006765745F4E6F77006765745F5469636B730054696D655370616E006765745F546F74616C4461797300436F6E7665727400546F496E743332004E756C6C0041646444617973006765745F4E756C6C004461794F665765656B006765745F4461794F665765656B00546F537472696E6700436F6E7461696E7300537562737472696E67005265706C616365006765745F4D6F6E746800496E743332006765745F44617900436F6E636174005061644C656674006765745F4D696C6C697365636F6E64005061645269676874006765745F486F7572006765745F4D696E757465006765745F4461794F6659656172006765745F54696D654F66446179006765745F5365636F6E6400546F53686F727454696D65537472696E67006765745F5965617200456E64735769746800537461727473576974680053797374656D2E546872656164696E6700546872656164006765745F43757272656E745468726561640053797374656D2E476C6F62616C697A6174696F6E0043756C74757265496E666F006765745F43757272656E7443756C747572650043616C656E646172006765745F43616C656E646172004461746554696D65466F726D6174496E666F006765745F4461746554696D65466F726D61740043616C656E6461725765656B52756C65006765745F43616C656E6461725765656B52756C65006765745F46697273744461794F665765656B004765745765656B4F66596561720053657269616C697A61626C654174747269627574650053716C55736572446566696E656441676772656761746541747472696275746500466F726D6174005374727563744C61796F7574417474726962757465004C61796F75744B696E64006765745F436F756E740041646400546F5570706572006F705F496E657175616C69747900417267756D656E74457863657074696F6E00546F41727261790049436F6C6C656374696F6E0041646452616E6765005265766572736500547970650052756E74696D655479706548616E646C65004765745479706546726F6D48616E646C65004172726179004A6F696E0052656164496E7433320052656164537472696E670049456E756D657261746F7200476574456E756D657261746F72006765745F43757272656E74004D6F76654E6578740049446973706F7361626C6500446973706F7365006F705F4578706C6963697400456D70747900497344696769740049734C6574746572006765745F4368617273004E756D626572466F726D6174496E666F006765745F43757272656E74496E666F006765745F4E756D626572446563696D616C536570617261746F72006765745F4E756D62657247726F7570536570617261746F7200547279506172736500466C6167734174747269627574650000000000010011250059002D0025006D002D0025006400010525006100000525006200000525006300000525004400000525006400000525006500000525006600000525004800000525006800000525004900000525006900000525006A00000525006B00000525006C00000525004D00000525006D00000525007000000541004D00000550004D0000052500720000052500530000052500730000052500540000052500550000052500750000052500560000052500760000052500570000052500770000052500580000052500780000052500590000052500790000052500250000032500000331000005730074000005740068000003320000056E0064000003330000057200640000074100530043000009440045005300430000094E00540052004C00003920006900730020006E006F0074002000760061006C0069006400200066006F007200200053006F007200740020004F007200640065007200007F49006E007400650072006E0061006C0020004500720072006F0072003A0020004E0075006D00650072006900630061006C00560061006C00750065002000630061006C006C006500640020006F006E002000610020006E006F006E0020006E0075006D00650072006900630061006C002000760061006C00750065002E000000169DD99F8BCEFD458FF6F813BC3F3C0B0008B77A5C561934E08905151215010E080002122111251229070002011C1011250A0003112D11251125112D0A0003113111251125112D0400001135040000122906000111311135060001113511310800021229113511250500010E1135050001081135032000010206080306110C040000000004010000000402000000040300000004040000000405000000040600000003061110040700000004080000000409000000040A000000040B000000040C000000030611140306123902060E09200301112511251125052001011118042000112505200101123D052001011241030612240306112C05200101112C052002080E0E07200208130013000400010803042001080E052002081C1C03061120030611450206030306121C05200101121C042001010E042000112004200011450320000E042800112004280011450328000E12010001005408074D617853697A65FFFFFFFF20010002005408074D617853697A65FFFFFFFF54020A49734E756C6C61626C650104200101085C01000300540E044E616D651153706C6974546F41727261795461626C65540E1146696C6C526F774D6574686F644E616D650746696C6C526F77540E0F5461626C65446566696E6974696F6E104944204E56415243484152284D4158290320000A042001030A0620011D0E1D030907041221021D0E1D03032000020306112D05000111250E080002112D1125112505000102112D050001112D02080002112D112D112D080003020E0E11808D050002020E0E0807050E0E02112D020707040E0E112D02050001113108072002080E1180910807050E0E08113102050000118095070001113511809504070111350407011229808F010001005455794D6963726F736F66742E53716C5365727665722E5365727665722E446174614163636573734B696E642C2053797374656D2E446174612C2056657273696F6E3D322E302E302E302C2043756C747572653D6E65757472616C2C205075626C69634B6579546F6B656E3D623737613563353631393334653038390A446174614163636573730100000005200011809506200301080808042001010A0320000D040001080D0E07051180951180950A118099113103061135032000080620011180950D0807031180951135020520001180A1042001020E0520020E08080520020E0E0E0500020E0E0E0520020E08030520001180990520010111250F07080E1180950E0E122902081180990907051180950E0E02080500001280A90520001280AD0520001280B10520001280B50520001180B90C2003081180951180B91180A11307071180951280AD1280B11180B91180A10802062001011180C51A010002000000010054080B4D61784279746553697A65FFFFFFFF062001011180CD042001081C030701020420001D1C062001011280D50520010112190800011280D91180DD0820011280E11280D9021D0E0600020E0E1D0E0507021125020507030808020520001280E50320001C0A07040E1280E5021280E90700020811451145050002080E0E040702080305000108114504070208020307010804070111200507021145020307010E040001020304200103080500001280ED070002020E101145060704080303020500011145080C070A080208080808020802081301000E4275677A696C6C612E4D5353514C00000501000000000801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F77730100007C55000000000000000000009E550000002000000000000000000000000000000000000000000000905500000000000000000000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF25002040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100100000001800008000000000000000000000000000000100010000003000008000000000000000000000000000000100000000004800000058600000B00200000000000000000000B00234000000560053005F00560045005200530049004F004E005F0049004E0046004F0000000000BD04EFFE0000010000000100EC6E9E0E00000100EC6E9E0E3F000000000000000400000002000000000000000000000000000000440000000100560061007200460069006C00650049006E0066006F00000000002400040000005400720061006E0073006C006100740069006F006E00000000000000B00410020000010053007400720069006E006700460069006C00650049006E0066006F000000EC010000010030003000300030003000340062003000000048000F000100460069006C0065004400650073006300720069007000740069006F006E00000000004200750067007A0069006C006C0061002E004D005300530051004C000000000040000F000100460069006C006500560065007200730069006F006E000000000031002E0030002E0033003700340032002E00320038003300390036000000000048001300010049006E007400650072006E0061006C004E0061006D00650000004200750067007A0069006C006C0061002E004D005300530051004C002E0064006C006C00000000002800020001004C006500670061006C0043006F0070007900720069006700680074000000200000005000130001004F0072006900670069006E0061006C00460069006C0065006E0061006D00650000004200750067007A0069006C006C0061002E004D005300530051004C002E0064006C006C000000000044000F000100500072006F006400750063007400560065007200730069006F006E00000031002E0030002E0033003700340032002E00320038003300390036000000000048000F00010041007300730065006D0062006C0079002000560065007200730069006F006E00000031002E0030002E0033003700340032002E0032003800330039003600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000C000000B03500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + WITH PERMISSION_SET = SAFE + |); + + $self->do("CREATE FUNCTION [dbo].[FROM_DAYS](\@days [int]) + RETURNS [datetime] WITH EXECUTE AS CALLER + AS + EXTERNAL NAME [Bugzilla.MSSQL].[UserDefinedFunctions].[FROM_DAYS] + "); + + $self->do("CREATE FUNCTION [dbo].[INSTR](\@find [nvarchar](max), \@inthis [nvarchar](max), \@CaseSensitive [bit]) + RETURNS [int] WITH EXECUTE AS CALLER + AS + EXTERNAL NAME [Bugzilla.MSSQL].[UserDefinedFunctions].[INSTR] + "); + + $self->do("CREATE FUNCTION [dbo].[NOT_REGEXP](\@subject [nvarchar](max), \@pattern [nvarchar](max), \@ignorecase [bit]) + RETURNS [bit] WITH EXECUTE AS CALLER + AS + EXTERNAL NAME [Bugzilla.MSSQL].[UserDefinedFunctions].[NOT_REGEXP] + "); + + $self->do("CREATE FUNCTION [dbo].[REGEXP](\@subject [nvarchar](max), \@pattern [nvarchar](max), \@ignorecase [bit]) + RETURNS [bit] WITH EXECUTE AS CALLER + AS + EXTERNAL NAME [Bugzilla.MSSQL].[UserDefinedFunctions].[REGEXP] + "); + + $self->do("CREATE FUNCTION [dbo].[TO_DAYS](\@date [DATETIME]) + RETURNS [int] WITH EXECUTE AS CALLER + AS + EXTERNAL NAME [Bugzilla.MSSQL].[UserDefinedFunctions].[TO_DAYS] + "); + + $self->do("CREATE FUNCTION [dbo].[DATE_FORMAT](\@Date [datetime], \@format [nvarchar](max)) + RETURNS [nvarchar](max) WITH EXECUTE AS CALLER + AS + EXTERNAL NAME [Bugzilla.MSSQL].[UserDefinedFunctions].[DATE_FORMAT] + "); + + $self->do("CREATE AGGREGATE [dbo].[GROUP_CONCAT] + (\@Value [nvarchar](max), \@Seperator [nvarchar](max), \@Sort [nvarchar](max)) + RETURNS[nvarchar](max) + EXTERNAL NAME [Bugzilla.MSSQL].[GROUP_CONCAT] + "); + + $self->do("IF EXISTS (SELECT name FROM sys.procedures WHERE name = N'sp_alter_column_default' and type = 'P') + DROP PROC [sp_alter_column_default]"); + + $self->do(q|CREATE PROC [sp_alter_column_default] + @table nvarchar(max), + @column nvarchar(max), + @value nvarchar(max) + AS + BEGIN + set nocount on + declare @constraint nvarchar(500) + select @constraint = df.name from + sys.tables t + inner join sys.default_constraints df on df.parent_object_id = t.object_id + inner join sys.columns col on parent_column_id = column_id + where col.name = @column and t.name = @table + + IF (@constraint is not null) BEGIN + exec('alter table '+@table+' drop constraint '+@constraint) + END + exec('alter table [dbo].['+@table+'] add default ('''+@value+''') for ['+@column+']') + END + |); + + $self->do("IF EXISTS (SELECT name FROM sys.procedures WHERE name = N'sp_alter_primarykey' and type = 'P') + DROP PROC [sp_alter_primarykey]"); + + $self->do(q|CREATE PROC sp_alter_primarykey + @table nvarchar(max), + @column nvarchar(max) + AS + BEGIN + SET NOCOUNT ON + DECLARE @constraint nvarchar(500) + SELECT @constraint = k.name FROM + sys.tables t + INNER JOIN sys.key_constraints k ON k.parent_object_id = t.object_id + WHERE t.name = @table AND k.type = 'PK' + + IF (@constraint IS NOT NULL) BEGIN + EXEC('ALTER TABLE [dbo].['+@table+'] DROP CONSTRAINT ['+@constraint+']') + END + EXEC('ALTER TABLE [dbo].['+@table+'] ADD PRIMARY KEY CLUSTERED (['+@column+'] ASC)') + END|); + + print "Done.\n"; + + # And now we create the tables and the Schema object. + $self->SUPER::bz_setup_database(); + +} + + + +sub _bz_add_field_table { + my ($self, $name, $schema_ref) = @_; + # We do nothing if the table already exists. + return if $self->bz_table_info($name); + + # Copy this so that we're not modifying the passed reference. + # (This avoids modifying a constant in Bugzilla::DB::Schema.) + my %table_schema = %$schema_ref; + my %indexes = @{ $table_schema{INDEXES} }; + my %fixed_indexes; + foreach my $key (keys %indexes) { + $fixed_indexes{$name . "_" . $key} = $indexes{$key}; + } + # INDEXES is supposed to be an arrayref, so we have to convert back. + my @indexes_array = %fixed_indexes; + $table_schema{INDEXES} = \@indexes_array; + # We add this to the abstract schema so that bz_add_table can find it. + $self->_bz_schema->add_table($name, \%table_schema); + $self->bz_add_table($name); +} + +sub bz_add_field_tables { + my ($self, $field) = @_; + + $self->_bz_add_field_table($field->name, + $self->_bz_schema->FIELD_TABLE_SCHEMA, $field->type); + if ($field->type == FIELD_TYPE_MULTI_SELECT) { + my $ms_table = "bug_" . $field->name; + $self->_bz_add_field_table($ms_table, + $self->_bz_schema->MULTI_SELECT_VALUE_TABLE); + + $self->updatecreate_column_refs; + } +} + +sub get_random_name() +{ + my @chars=('a'..'z','A'..'Z'); + my $random_string; + foreach (1..22) + { + $random_string .= $chars[rand @chars]; + } + return $random_string; +} + + +############################### + +sub adjust_statement { + my ($sql) = @_; + + # We can't just assume any occurrence of "''" in $sql is an empty + # string, since "''" can occur inside a string literal as a way of + # escaping a single "'" in the literal. Therefore we must be trickier... + + # split the statement into parts by single-quotes. The negative value + # at the end to the split operator from dropping trailing empty strings + # (e.g., when $sql ends in "''") + my @parts = split /'/, $sql, -1; + + if( !(@parts % 2) ) { + # Either the string is empty or the quotes are mismatched + # Returning input unmodified. + return $sql; + } + + # We already verified that we have an odd number of parts. If we take + # the first part off now, we know we're entering the loop with an even + # number of parts + my @result; + my $part = shift @parts; + + my $is_select = ($part =~ m/^\s*SELECT\b/io); + my $has_from = ($part =~ m/\bFROM\b/io) if $is_select; + + # MSSQL doesn't have LIMIT, so if we find the LIMIT comment, wrap the + # query with "SELECT * FROM (...) WHERE rownum < $limit" + my ($limit,$offset) = ($part =~ m{/\* LIMIT (\d*) (\d*) \*/}o); + + push @result, $part; + while( @parts ) { + my $string = shift @parts; + my $nonstring = shift @parts; + + # if the non-string part is zero-length and there are more parts left, + # then this is an escaped quote inside a string literal + while( !(length $nonstring) && @parts ) { + # we know it's safe to remove two parts at a time, since we + # entered the loop with an even number of parts + $string .= "''" . shift @parts; + $nonstring = shift @parts; + } + + # Look for a FROM if this is a SELECT and we haven't found one yet + my $has_from = ($nonstring =~ m/\bFROM\b/io) + if ($is_select and !$has_from); + + # Look for a FROM if this is a SELECT and we haven't found one yet + $has_from = ($nonstring =~ m/\bFROM\b/io) + if ($is_select and !$has_from); + + # Look for a LIMIT clause + ($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o); + + push @result, $string; + push @result, $nonstring; + } + + my $new_sql = join "'", @result; + + # Wrap the query with a "WHERE rownum <= ..." if we found LIMIT + + if (defined($limit)) { + if ($new_sql !~ /\bWHERE\b/) { + $new_sql = $new_sql." WHERE 1=1"; + } + my ($before_where, $after_where) = split /\bWHERE\b/i,$new_sql; + if (defined($offset)) { + if ($new_sql =~ /(.*\s+)FROM(\s+.*)/i) { + my ($before_from,$after_from) = ($1,$2); + + #Using a Randomly Generated String to create 'AS' name for the subquery + my $random_name = get_random_name(); + + $before_where = "$before_from FROM ($before_from," + . " ROW_NUMBER() OVER (ORDER BY (SELECT 1)) R " + . " FROM $after_from ) AS $random_name"; + $after_where = " $random_name.R BETWEEN $offset+1 AND $limit+$offset"; + } + } else { + $before_where =~ s/^(select +(distinct)?)/$1 top $limit /i; + } + + $new_sql = $before_where." WHERE ".$after_where; + } + + $new_sql =~ s/CURRENT_DATE\(?\)?/CAST(GETDATE() as DATE)/igo; + $new_sql =~ s/LOCALTIMESTAMP\(0\)/GETDATE()/igo; # Might have to change this is we do more than (0) in the future + $new_sql =~ s/NOW\(\)/GETDATE()/igo; + + ## Date, Time and DateTime handing for INTERVAL math operations + # First: Check for multiple intervals for a single date using CAST, typically casting as DATE or TIME(0) + # Second: Check for multiple intervals for a single date not using CAST + # Third: Check for single use of interval for a single date using CAST, typically casting as DATE or TIME(0) + # Fourth: Check for single use of intervas for a single date not using CAST + + $new_sql =~ s/\(?CAST\((\b[\w\.]+\(?\)?) as (\w+\(?\)?)\) (\+|\-) \/\*INTERVAL (\d+|\?) (\b\w+\b)\*\/\)? (\+|\-) \/\*INTERVAL (\d+|\?) (\b\w+\b)\*\//CAST(DATEADD($5, $3$4, DATEADD($8, $6CAST($7 as int), $1)) as $2 )/; + $new_sql =~ s/\(?(\b[\w\.]+\(?\)?) (\+|\-) \/\*INTERVAL (\d+|\?) (\b\w+\b)\*\/\)? (\+|\-) \/\*INTERVAL (\d+|\?) (\b\w+\b)\*\//DATEADD($4, $2CAST($3 as int), DATEADD($7, $5$6, $1))/; + $new_sql =~ s/CAST\((\b[\w\.]+\(?\)?) as (\w+\(?\)?)\) (\+|\-) \/\*INTERVAL (\d+|\?) (\b\w+\b)\*\//CAST(DATEADD($5, $3CAST($4 as int),$1) as $2 )/; + $new_sql =~ s/(\b[\w\.]+\(?\)?) (\+|\-) \/\*INTERVAL (\d+|\?) (\w+)\*\//DATEADD($4, $2CAST($3 AS int), $1)/igo; + + + $new_sql =~ s/\bLENGTH\b\((.*)\)/LEN($1)/igo; + $new_sql =~ s/SUBSTRING\((.*) FROM (\d+) FOR (\d+)\)/SUBSTRING($1, $2, $3)/igo; + + return $new_sql; +} + +sub do { + my $self = shift; + my $sql = shift; + $sql = adjust_statement($sql); + unshift @_, $sql; + return $self->SUPER::do(@_); +} + +sub selectrow_array { + my $self = shift; + my $stmt = shift; + my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt); + unshift @_, $new_stmt; + if ( wantarray ) { + my @row = $self->SUPER::selectrow_array(@_); + return @row; + } else { + my $row = $self->SUPER::selectrow_array(@_); + return $row; + } +} + +sub selectrow_arrayref { + my $self = shift; + my $stmt = shift; + my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt); + unshift @_, $new_stmt; + my $ref = $self->SUPER::selectrow_arrayref(@_); + return undef if !defined $ref; + return $ref; +} + +sub selectrow_hashref { + my $self = shift; + my $stmt = shift; + my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt); + unshift @_, $new_stmt; + my $ref = $self->SUPER::selectrow_hashref(@_); + return undef if !defined $ref; + return $ref; +} + +sub selectall_arrayref { + my $self = shift; + my $stmt = shift; + my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt); + unshift @_, $new_stmt; + + my $ref = $self->SUPER::selectall_arrayref(@_); + return undef if !defined $ref; + return $ref; +} + +sub selectall_hashref { + my $self = shift; + my $stmt = shift; + my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt); + unshift @_, $new_stmt; + my $rows = $self->SUPER::selectall_hashref(@_); + return undef if !defined $rows; + return $rows; +} + +sub selectcol_arrayref { + my $self = shift; + my $stmt = shift; + my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt); + unshift @_, $new_stmt; + my $ref = $self->SUPER::selectcol_arrayref(@_); + return undef if !defined $ref; + return $ref; +} + +sub prepare { + my $self = shift; + my $sql = shift; + my $new_sql = adjust_statement($sql); + unshift @_, $new_sql; + return bless $self->SUPER::prepare(@_), 'Bugzilla::DB::Mssql::st'; +} + +sub prepare_cached { + my $self = shift; + my $sql = shift; + my $new_sql = adjust_statement($sql); + unshift @_, $new_sql; + return bless $self->SUPER::prepare_cached(@_), 'Bugzilla::DB::Mssql::st'; +} + +sub db_lock { + my ($dbh, $lock_name, $lock_timeout) = @_; + return unless $dbh; + die "db_lock requires a lock name" unless $lock_name; + $lock_timeout ||= 0; ## return error right away if lock can't be granted and no timeout specified. + + my $lockok; + + $lock_timeout *= 1000; ## SQL Server default time is milliseconds. standardize to seconds. + my $lock_sql = q| DECLARE @res int; exec @res = sp_getapplock @Resource = ?, @LockMode = 'Exclusive', @DbPrincipal = ?, @LockOwner = 'session', @LockTimeout = ? ; SELECT @res; |; + my $sth = $dbh->prepare($lock_sql); + my $ret = $sth->execute($lock_name, $dbh->{Username}, $lock_timeout ); + my ($result) = $sth->fetchrow_array; + $sth->finish; + if ($result >= 0) { + $locks{$lock_name} = 1; + return 1; + } else { + return 0; + } + return $lockok; +} + +sub db_unlock { + my ($dbh, $lock_name) = @_; + return unless $dbh; + die "db_unlock requires a lock name" unless $lock_name; + ## SQL Server throws a fatal error if you try to release a lock you don't have, so don't try unless we have one. + if ($locks{$lock_name}) { + my $lock_sql = q| DECLARE @res int; exec @res = sp_releaseapplock @Resource = ?, @LockOwner = 'session', @DbPrincipal = ?; SELECT @res; |; + my $raise = $dbh->{RaiseError}; + $dbh->{RaiseError} = 0; + my $sth = $dbh->prepare($lock_sql); + my $ret = $sth->execute($lock_name, $dbh->{Username} ); + return if $dbh->err; + my ($result) = $sth->fetchrow_array; + $sth->finish; + $dbh->{RaiseError} = $raise; + delete $locks{$lock_name}; + return 1; + } else { + return 0; + } +} + +# Should really live in it's own .pm file... +# DNI function override for MSSQL sql adjustments +package Bugzilla::DB::Mssql::st; +use base qw(DBI::st); + +sub fetchrow_arrayref { + my $self = shift; + my $ref = $self->SUPER::fetchrow_arrayref(@_); + return undef if !defined $ref; + return $ref; +} + +sub fetchrow_array { + my $self = shift; + if ( wantarray ) { + my @row = $self->SUPER::fetchrow_array(@_); + return @row; + } else { + my $row = $self->SUPER::fetchrow_array(@_); + return $row; + } +} + +sub fetchrow_hashref { + my $self = shift; + my $ref = $self->SUPER::fetchrow_hashref(@_); + return undef if !defined $ref; + return $ref; +} + +sub fetchall_arrayref { + my $self = shift; + my $ref = $self->SUPER::fetchall_arrayref(@_); + return undef if !defined $ref; + return $ref; +} + +sub fetchall_hashref { + my $self = shift; + my $ref = $self->SUPER::fetchall_hashref(@_); + return undef if !defined $ref; + return $ref; +} + +sub fetch { + my $self = shift; + my $row = $self->SUPER::fetch(@_); + return $row; +} +1; === added file Bugzilla/DB/Schema/Mssql.pm --- Bugzilla/DB/Schema/Mssql.pm 1970-01-01 00:00:00 +0000 +++ Bugzilla/DB/Schema/Mssql.pm 2010-02-25 23:00:47 +0000 @@ -1,0 +1,528 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Netscape Communications +# Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# Michael Thomas + + +# Many of the functions in this module were copied from Mysql.pm and Oracle.pm then altered for MSSQL + +package Bugzilla::DB::Schema::Mssql; + +############################################################################### +# +# DB::Schema implementation for MySQL +# +############################################################################### + +use strict; +use Bugzilla::Error; +use Carp qw(confess); +use base qw(Bugzilla::DB::Schema); +use Digest::MD5 qw(md5_hex); + +# This is for column_info_to_column, to know when a tinyint is a +# boolean and when it's really a tinyint. This only has to be accurate +# up to and through 2.19.3, because that's the only time we need +# column_info_to_column. +# +# This is basically a hash of tables/columns, with one entry for each column +# that should be interpreted as a BOOLEAN instead of as an INT1 when +# reading in the Schema from the disk. The values are discarded; I just +# used "1" for simplicity. +use constant ADD_COLUMN => 'ADD'; +use constant BOOLEAN_MAP => { + bugs => {everconfirmed => 1, reporter_accessible => 1, + cclist_accessible => 1, qacontact_accessible => 1, + assignee_accessible => 1}, + longdescs => {isprivate => 1, already_wrapped => 1}, + attachments => {ispatch => 1, isobsolete => 1, isprivate => 1}, + flags => {is_active => 1}, + flagtypes => {is_active => 1, is_requestable => 1, + is_requesteeble => 1, is_multiplicable => 1}, + fielddefs => {mailhead => 1, obsolete => 1}, + bug_status => {isactive => 1}, + resolution => {isactive => 1}, + bug_severity => {isactive => 1}, + priority => {isactive => 1}, + rep_platform => {isactive => 1}, + op_sys => {isactive => 1}, + profiles => {mybugslink => 1, newemailtech => 1}, + namedqueries => {linkinfooter => 1, watchfordiffs => 1}, + groups => {isbuggroup => 1, isactive => 1}, + group_control_map => {entry => 1, membercontrol => 1, othercontrol => 1, + canedit => 1}, + group_group_map => {isbless => 1}, + user_group_map => {isbless => 1, isderived => 1}, + products => {disallownew => 1}, + series => {public => 1}, + whine_queries => {onemailperbug => 1}, + quips => {approved => 1}, + setting => {is_enabled => 1} +}; + +sub _initialize { + + my $self = shift; + + $self = $self->SUPER::_initialize(@_); + + $self->{db_specific} = { + + BOOLEAN => 'SMALLINT', + FALSE => '0', + TRUE => '1', + + INT1 => 'int', + INT2 => 'int', + INT3 => 'int', + INT4 => 'int', + + SMALLSERIAL => '[INT] IDENTITY(1,1)', + MEDIUMSERIAL => '[INT] IDENTITY(1,1)', + INTSERIAL => '[INT] IDENTITY(1,1)', + + TINYTEXT => 'nvarchar(255)', + MEDIUMTEXT => 'nvarchar(4000)', + LONGTEXT => 'nvarchar(max)', + + LONGBLOB => 'varbinary(max)', + + DATETIME => 'smalldatetime', + + varchar => 'nvarchar', + + + }; + + $self->_adjust_schema; + + return $self; + +} + +sub _adjust_schema { + + my $self = shift; + + # The _initialize method has already set up the db_specific hash with + # the information on how to implement the abstract data types for the + # instantiated DBMS-specific subclass. + my $db_specific = $self->{db_specific}; + + # Loop over each table check for a primary key; + foreach my $table (keys %{ $self->{schema} }) { + my %fields = (@{ $self->{schema}{$table}{FIELDS} }); + + # Check if we have a primary key + my $has_pk; + foreach my $field_def (values %fields) { + if (exists($field_def->{PRIMARYKEY})){ + $has_pk = 1; + } + } + + unless ($has_pk){ + push @{ $self->{schema}{$table}{FIELDS} }, "$table\_id" => { TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1, }; + } + } + + # Loop over each table in the abstract database schema. + foreach my $table (keys %{ $self->{schema} }) { + my %fields = (@{ $self->{schema}{$table}{FIELDS} }); + # Loop over the field definitions in each table. + foreach my $field_def (values %fields) { + # Find the data type, allows for (xyz) in {TYPE}, making sure to match from the beginning of the string + $field_def->{TYPE} =~ m/^(\w*)/; + my $datatype = $1 if $1; + + # If the field type is an abstract data type defined in the + # $db_specific hash, replace it with the DBMS-specific data type + # that implements it. + if (exists($db_specific->{$datatype})) { + $field_def->{TYPE} =~ s/^$datatype/$db_specific->{$datatype}/; + } + # Replace abstract default values (such as 'TRUE' and 'FALSE') + # with their database-specific implementations. + if (exists($field_def->{DEFAULT}) + && exists($db_specific->{$field_def->{DEFAULT}})) { + $field_def->{DEFAULT} = $db_specific->{$field_def->{DEFAULT}}; + } + } + } + + return $self; + +} + +sub _get_add_indexes_ddl { + my($self, @indexes) = @_; + + +} + +sub get_add_fk_sql { + my ($self, $table, $column, $def) = @_; + + my $fk_string = $self->get_fk_ddl($table, $column, $def); + my @sql = $self->create_trigger_ddl($table, $column, $def); + push @sql, "ALTER TABLE $table ADD $fk_string" if $fk_string; + return join("\n", @sql); +} + +sub create_trigger_ddl { + my ($self, $table, $column, $references) = @_; + + my $ref_table = $references->{TABLE}; + my $ref_column = $references->{COLUMN}; + my @tr_sql; + foreach my $ACTION (qw{UPDATE DELETE}){ + next unless $references->{$ACTION}; + my $name = $self->_get_trigger_name($ACTION,$references->{$ACTION},$ref_table,$ref_column,$table,$column); + if ($references->{$ACTION} eq 'CASCADE'){ + my $sql .= "CREATE TRIGGER [$name] ON [$ref_table]\n"; + $sql .= " FOR $ACTION\n"; + $sql .= " AS\n"; + $sql .= " SET NOCOUNT ON\n"; + $sql .= " IF UPDATE([$ref_column])\n" if $ACTION eq 'UPDATE'; + $sql .= " BEGIN\n"; + $sql .= " DECLARE \@NEW NVARCHAR(max)\n" if $ACTION eq 'UPDATE'; + $sql .= " DECLARE \@OLD NVARCHAR(max)\n"; + $sql .= " SELECT \@NEW = [$ref_column] FROM INSERTED\n" if $ACTION eq 'UPDATE'; + $sql .= " SELECT \@OLD = [$ref_column] FROM DELETED\n\n"; + $sql .= " IF (\@NEW <> \@OLD) BEGIN\n" if $ACTION eq 'UPDATE'; + $sql .= " UPDATE [$table] SET [$column] = \@NEW WHERE $column = \@OLD\n" if $ACTION eq 'UPDATE'; + $sql .= " DELETE FROM [$table] WHERE $column = \@OLD\n" if $ACTION eq 'DELETE'; + $sql .= " END\n" if $ACTION eq 'UPDATE'; + $sql .= " END\n"; + push(@tr_sql, $sql); + } elsif ($ACTION eq 'DELETE' && $references->{$ACTION} eq 'SET NULL'){ + my $sql .= "CREATE TRIGGER [$name] ON [$ref_table]\n"; + $sql .= " FOR $ACTION\n"; + $sql .= " AS\n"; + $sql .= " SET NOCOUNT ON\n"; + $sql .= " BEGIN\n"; + $sql .= " DECLARE \@OLD NVARCHAR(max)\n"; + $sql .= " SELECT \@OLD = [$ref_column] FROM DELETED\n\n"; + $sql .= " UPDATE [$table] SET [$column] = NULL WHERE $column = \@OLD\n"; + $sql .= " END\n"; + push(@tr_sql, $sql); + } + } + + return @tr_sql; + +} + +sub get_table_ddl { + +=item C + + Description: Public method to generate the SQL statements needed to create + the a given table and its indexes in the Bugzilla database. + Subclasses may override or extend this method, if needed, but + subclasses probably should override C<_get_create_table_ddl> + or C<_get_create_index_ddl> instead. + Parameters: $table - the table name + Returns: an array of strings containing SQL statements + +=cut + + my($self, $table) = @_; + my @ddl = (); + + die "Table $table does not exist in the database schema." + unless (ref($self->{schema}{$table})); + + my $create_table = $self->_get_create_table_ddl($table); + push(@ddl, $create_table) if $create_table; + + my @indexes = @{ $self->{schema}{$table}{INDEXES} || [] }; + my @ft_columns; + while (@indexes) { + my $index_name = shift(@indexes); + my $index_info = shift(@indexes); + if (ref($index_info) eq 'HASH'){ + if ($index_info->{TYPE} eq 'FULLTEXT') { + push @ft_columns, join (',',@{$index_info->{FIELDS}}); + next; + } + } + my $index_sql = $self->get_add_index_ddl($table, $index_name, $index_info); + push(@ddl, $index_sql) if $index_sql; + } + + if (@ft_columns) { + my $fulltext_sql = $self->get_add_fulltext($table, @ft_columns); + push(@ddl, $fulltext_sql); + + } + + push(@ddl, @{ $self->{schema}{$table}{DB_EXTRAS} }) + if (ref($self->{schema}{$table}{DB_EXTRAS})); + return @ddl; + +} + +sub get_add_fulltext { + + my ($self, $table_name, @ft_table_columns) = @_; + + #FullText indexes require a normal index too, so will make one even if one exists. + my $sql .= "IF NOT EXISTS (select 1 from sys.fulltext_catalogs WHERE name = 'bugs') BEGIN\n"; + $sql .= " CREATE FULLTEXT CATALOG bugs AS DEFAULT;\n"; + $sql .= "END\n"; + $sql .= "IF EXISTS (SELECT [unique_index_id] FROM sys.fulltext_indexes WHERE [object_id]=object_id('$table_name')) BEGIN\n"; + $sql .= " DROP FULLTEXT INDEX ON [$table_name]\n"; + $sql .= "END\n"; + $sql .= "DECLARE \@PK_Index NVARCHAR(200), \@ACTION nvarchar(50)\n"; + $sql .= "SELECT \@PK_Index = name FROM sys.indexes WHERE [object_id]=object_id('$table_name') and name LIKE 'PK_%'\n"; + $sql .= "EXEC( 'CREATE FULLTEXT INDEX ON [$table_name] ([".join("], [", @ft_table_columns)."])\n"; + $sql .= "KEY INDEX ['+\@PK_Index+']\n"; + $sql .= "ON bugs')"; + + return $sql; +} + +sub _get_create_table_ddl { + my($self, $table) = @_; + + my $thash = $self->{schema}{$table}; + die "Table $table does not exist in the database schema." + unless (ref($thash)); + + my $create_table = "CREATE TABLE $table \(\n"; + + my @fields = @{ $thash->{FIELDS} }; + while (@fields) { + my $field = shift(@fields); + my $finfo = shift(@fields); + + $create_table .= "\t[$field]\t" . $self->get_type_ddl($finfo); + $create_table .= "," if (@fields); + $create_table .= "\n"; + } + + $create_table .= "\)"; + + return($create_table) + +} +#------------------------------------------------------------------------------ +sub _get_create_index_ddl { + + my($self, $table_name, $index_name, $index_fields, $index_type) = @_; + my $sql = ''; + + # If all fields are NOT NULL then the index should be a standard index + # If it is mixed then ignore nulls + my $allownull; + foreach my $field (@$index_fields) { + my %col_def = %{ $self->get_column_abstract($table_name, $field)}; + if ( !defined $col_def{NOTNULL} || defined $col_def{NOTNULL} && $col_def{NOTNULL} == 0){ + $allownull = 1; + last; + } + } + + $sql .= "CREATE "; + $sql .= "$index_type " if $index_type eq 'UNIQUE'; + $sql .= "INDEX [$index_name] ON [$table_name] ([".join("], [", @$index_fields)."])\n"; + + # Make it a filtered index IF we allow nulls certain conditions + # In the case of multiple fields NOT NULL only allies when all fields are null else standard rules apply. + $sql .= "WHERE [".join('] IS NOT NULL AND [', @$index_fields)."] IS NOT NULL\n" if $allownull; + + return($sql); + +} #eosub--_get_create_index_ddl +#-------------------------------------------------------------------- + +sub get_create_database_sql { + my ($self, $name) = @_; + + return ("CREATE DATABASE [$name] "); +} + +sub get_set_serial_sql { + my ($self, $table, $column, $value) = @_; + return ("DBCC CHECKIDENT ($table, RESEED, $value)"); +} + +sub get_alter_column_ddl { + my ($self, $table, $column, $new_def, $set_nulls_to) = @_; + my $old_def = $self->get_column($table, $column); + my %new_def_copy = %$new_def; + if ($old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) { + delete $new_def_copy{PRIMARYKEY}; + } + + my $new_ddl = $self->get_alter_type_ddl(\%new_def_copy); + my @statements; + + push(@statements, "UPDATE [$table] SET [$column] = $set_nulls_to + WHERE [$column] IS NULL") if defined $set_nulls_to; + push(@statements, "ALTER TABLE [$table] ALTER COLUMN [$column] $new_ddl"); + + my $default = $new_def_copy{DEFAULT}; + # Replace any abstract default value (such as 'TRUE' or 'FALSE') + # with its database-specific implementation. + if ( defined $default && exists($self->{db_specific}->{$default}) ) { + $default = $self->{db_specific}->{$default}; + } + + # first drops existing default if any + push(@statements, "sp_alter_column_default '$table', '$column', '$default'") if defined $default; + + my $primarykey = $new_def_copy{PRIMARYKEY}; + # first drops existing primary key if any + push(@statements, "sp_alter_primarykey '$table', '$column'") if defined $primarykey; + + return @statements; +} + +sub get_alter_type_ddl { + + my $self = shift; + my $finfo = (@_ == 1 && ref($_[0]) eq 'HASH') ? $_[0] : { @_ }; + my $type = $finfo->{TYPE}; + confess "A valid TYPE was not specified for this column (got " + . Dumper($finfo) . ")" unless ($type); + + my $type_ddl = $self->convert_type($type); + $type_ddl .= " NOT NULL" if ($finfo->{NOTNULL}); + return($type_ddl); +} + +sub get_drop_fk_sql { + my ($self, $table, $column, $references) = @_; + my $sql = ''; + + $references->{UPDATE} ||= 'RESTRICT'; + $references->{DELETE} ||= 'RESTRICT'; + + if ($references->{UPDATE} eq 'RESTRICT' || $references->{DELETE} eq 'RESTRICT' ) { + my $fk_name = $self->_get_fk_name($table, $column, $references); + $sql .= "IF EXISTS (SELECT name FROM sys.foreign_keys WHERE name = '$fk_name') BEGIN\n" + . " ALTER TABLE [$table] DROP CONSTRAINT [$fk_name]\n" + . "END\n"; + } + + my $ref_table = $references->{TABLE}; + my $ref_column = $references->{COLUMN}; + foreach my $ACTION (qw{UPDATE DELETE}){ + if ($references->{$ACTION} eq 'CASCADE' || $references->{$ACTION} eq 'SET NULL') { + my $name = $self->_get_trigger_name($ACTION,$references->{$ACTION},$ref_table,$ref_column,$table,$column); + $sql .= "IF EXISTS (SELECT name FROM sys.triggers WHERE name = '$name') BEGIN\n" + . " DROP TRIGGER [$name]\n" + . "END\n"; + } + } + return $sql; +} + +sub get_drop_index_ddl { + my ($self, $table, $name) = @_; + return ("DROP INDEX [$table].[$name]"); +} + +sub get_rename_column_ddl { + my ($self, $table, $old_name, $new_name) = @_; + return ("EXEC sp_rename '$table.$old_name' '$new_name' 'COLUMN'"); +} + +sub get_drop_column_ddl { + my ($self, $table, $column) = @_; + my $sql = "DECLARE \@default NVARCHAR(255) + SELECT \@default = d.name FROM sys.default_constraints d + INNER JOIN sys.columns c + ON d.parent_column_id = c.column_id + WHERE d.parent_object_id = OBJECT_ID(N'dbo.$table', N'U') + AND c.name = '$column' + IF (\@default is not null) BEGIN + EXEC('ALTER TABLE [$table] DROP CONSTRAINT ['+\@default+']') + END + ALTER TABLE [$table] DROP COLUMN [$column]\n"; + return ($sql); +} + +sub _get_fk_name { + my ($self, $table, $column, $references) = @_; + my $to_table = $references->{TABLE}; + my $to_column = $references->{COLUMN}; + + my $name = "fk_${to_table}_${to_column}_${table}_${column}"; + if (length($name) > $self->MAX_IDENTIFIER_LEN) { + $name = 'fk_' . $self->_hash_identifier($name); + } + + return $name; +} + +sub _get_trigger_name { + my ($self, $ACTION,$ACTION_TYPE,$ref_table,$ref_column,$table,$column) = @_; + + my $name = "tr_$ACTION\_$ACTION_TYPE\_$ref_table\_$ref_column\_$table\_$column"; + if (length($name) > $self->MAX_IDENTIFIER_LEN) { + $name = 'tr_' . $self->_hash_identifier($name); + } + + return $name; +} + +sub _hash_identifier { + my ($invocant, $value) = @_; + # We do -7 to allow prefixes like "idx_" or "fk_", or perhaps something + # longer in the future. + return substr(md5_hex($value), 0, $invocant->MAX_IDENTIFIER_LEN - 7); +} + +sub get_fk_ddl { + + my ($self, $table, $column, $references) = @_; + return "" if !$references; + + my $ref_table = $references->{TABLE}; + my $ref_column = $references->{COLUMN}; + my $delete = $references->{DELETE}; + my $update = $references->{UPDATE}; + + my $fk_name = $self->_get_fk_name($table, $column, $references); + my $sql = "\n CONSTRAINT [$fk_name] FOREIGN KEY ($column)\n" + . " REFERENCES $ref_table($ref_column)\n"; + $sql .=" ON UPDATE $update" if $update && $update ne 'CASCADE'; + $sql .=" ON DELETE $delete" if $delete && $delete ne 'CASCADE' && $delete ne 'SET NULL'; + + return ($delete && $delete ne 'CASCADE' && $delete ne 'SET NULL' + || $update && $update ne 'CASCADE' ? $sql : ''); +} + +sub get_add_column_ddl { + my ($self, $table, $column, $definition, $init_value) = @_; + my @statements; + push(@statements, "ALTER TABLE $table ". $self->ADD_COLUMN ." $column " . + $self->get_type_ddl($definition)); + + (push(@statements, "UPDATE $table SET $column = $init_value")) + if defined $init_value; + + return (@statements); +} + +1; === modified file Bugzilla/Install/Util.pm --- Bugzilla/Install/Util.pm 2010-03-28 21:20:32 +0000 +++ Bugzilla/Install/Util.pm 2010-03-31 23:04:51 +0000 @@ -583,6 +583,40 @@ } } +if (ON_WINDOWS && require Win32::API){ + my (undef, $major) = Win32::GetOSVersion(); + if ($major >= 6) { + CORE::GLOBAL::symlink = sub { + my ( $relative_target , $link_name, $isdir) = @_; + defined $link_name or die ('link not defined'); + defined $relative_target or die ('target not defined'); + # Return Value: + # If success, the return value is nonzero. + # If failure, the return value is zero. + # + # At Present SymLink is not support in the Perl Core for Windows + # However Symlink support was added to Windows as of Windows Vista + # + # Win32::API will alow us to import kernel32.dll and hook the + # CreateSymbolicLink function + # + # Function Structure: + # CreateSymbolicLink( string symlink_name, + # string target, + # boolean (0 = false, 1 = true) is_dir) + # + # http://msdn.microsoft.com/en-us/library/aa363866%28VS.85%29.aspx + # + my $createsymboliclink = Win32::API->new('kernel32', "CreateSymbolicLink",['P','P','N'],'I'); + return $createsymboliclink->Call($link_name,$relative_target, (-f $relative_target ? 0 : 1) ); + } + } else { + # Prevents useless errors in Pre-Vista environments + # Effectively tell tell the caller that we succeeded even though we never tried + CORE::GLOBAL::symlink = sub { return 1; } + } +} + # This is like request_cache, but it's used only by installation code # for checksetup.pl and things like that. our $_cache = {}; === modified file Bugzilla/Object.pm --- Bugzilla/Object.pm 2010-02-23 00:09:51 +0000 +++ Bugzilla/Object.pm 2010-03-31 22:41:51 +0000 @@ -333,6 +333,10 @@ my (@update_columns, @values, %changes); foreach my $column ($self->UPDATE_COLUMNS) { my ($old, $new) = ($old_self->{$column}, $self->{$column}); + + # Fix Dates + $new = format_datetime($new) if $date{$column} && defined $new; + # This has to be written this way in order to allow us to set a field # from undef or to undef, and avoid warnings about comparing an undef # with the "eq" operator. === modified file Bugzilla/Template.pm --- Bugzilla/Template.pm 2010-03-28 21:25:08 +0000 +++ Bugzilla/Template.pm 2010-03-31 22:50:21 +0000 @@ -856,12 +856,11 @@ # *compiled* versions using the full absolute path under the data/template # directory. (Like data/template/var/www/html/bugzilla/.) To avoid # re-compiling templates under mod_perl, we symlink to the - # already-compiled templates. This doesn't work on Windows. - if (!ON_WINDOWS) { - # We do these separately in case they're in different locations. - _do_template_symlink(bz_locations()->{'templatedir'}); - _do_template_symlink(bz_locations()->{'extensionsdir'}); - } + # already-compiled templates. + + # We do these separately in case they're in different locations. + _do_template_symlink(bz_locations()->{'templatedir'}); + _do_template_symlink(bz_locations()->{'extensionsdir'}); # If anything created a Template object before now, clear it out. delete Bugzilla->request_cache->{template}; @@ -890,6 +889,26 @@ return if ($abs_path eq $dir_to_symlink); my $abs_root = dirname($abs_path); + + if (ON_WINDOWS) { + # Junctions and Symlinks on Windows require a NTFS filesystem + return unless Win32::FsType() =~ m/NTFS/i; + + # On windows abs_root contains the volume letter and a : + # e.g. "C:/path/to/bugzilla/" + # + # If left as is, when we generate the container path we would get + # an improperly formatted path which would be rejected by the + # file system + # e.g. "C:/path/to/bugzilla/data/templateC:/path/to/bugzilla/" + # + # So we will need to strip the : and add a leading / to abs_root + # giving us a properly formatted path + # e.g. "C:/path/to/bugzilla/data/template/C/path/to/bugzilla/" + $abs_root =~ s/^(\w):/\/$1/; + } + + my $dir_name = basename($abs_path); my $datadir = bz_locations()->{'datadir'}; my $container = "$datadir/template$abs_root"; === modified file Bugzilla/Token.pm --- Bugzilla/Token.pm 2009-12-31 12:53:19 +0000 +++ Bugzilla/Token.pm 2010-03-31 23:18:15 +0000 @@ -94,9 +94,9 @@ my ($user, $old_email, $new_email) = @_; my $email_suffix = Bugzilla->params->{'emailsuffix'}; - my ($token, $token_ts) = _create_token($user->id, 'emailold', $old_email . ":" . $new_email); + my ($token, $token_ts) = _create_token($user->id, 'emailold', $old_email); - my $newtoken = _create_token($user->id, 'emailnew', $old_email . ":" . $new_email); + my $newtoken = _create_token($user->id, 'emailnew', $new_email); # Mail the user the token along with instructions for using it. === modified file Bugzilla/User.pm --- Bugzilla/User.pm 2010-02-18 00:16:31 +0000 +++ Bugzilla/User.pm 2010-03-31 22:45:34 +0000 @@ -1667,25 +1667,24 @@ trick_taint($username); # Reject if the new login is part of an email change which is # still in progress - # - # substring/locate stuff: bug 165221; this used to use regexes, but that - # was unsafe and required weird escaping; using substring to pull out - # the new/old email addresses and sql_position() to find the delimiter (':') - # is cleaner/safer - my $eventdata = $dbh->selectrow_array( - "SELECT eventdata - FROM tokens - WHERE (tokentype = 'emailold' - AND SUBSTRING(eventdata, 1, (" . - $dbh->sql_position(q{':'}, 'eventdata') . "- 1)) = ?) - OR (tokentype = 'emailnew' - AND SUBSTRING(eventdata, (" . - $dbh->sql_position(q{':'}, 'eventdata') . "+ 1), LENGTH(eventdata)) = ?)", - undef, ($username, $username)); - - if ($eventdata) { - # Allow thru owner of token - if($old_username && ($eventdata eq "$old_username:$username")) { + + my ($userid, $old_email) = $dbh->selectrow_array( + "SELECT userid, eventdata + FROM tokens + WHERE tokentype = 'emailold' + AND eventdata = ?", + undef, ($username)); + + my ($new_email) = $dbh->selectrow_array( + "SELECT eventdata + FROM tokens + WHERE tokentype = 'emailnew' + AND userid = ?", + undef, ($userid)); + + if ( $old_email || $new_email ) { + # Allow thru owner of token + if(($old_username && $old_username eq $old_email) || ($new_email && $new_email eq $username)) { return 1; } return 0; === modified file Bugzilla/Util.pm --- Bugzilla/Util.pm 2010-03-14 00:34:43 +0000 +++ Bugzilla/Util.pm 2010-03-31 22:46:33 +0000 @@ -44,7 +44,8 @@ file_mod_time is_7bit_clean bz_crypt generate_random_password validate_email_syntax clean_text - get_text template_var disable_utf8); + get_text template_var disable_utf8 + format_datetime); use Bugzilla::Constants; @@ -410,6 +411,23 @@ return $wrapped; } +sub format_datetime { + # Assumes is defined + my $timestring = shift; + + # Enforce Basic Time Format for DataType DateTime + # HH:MM:SS (optional micro seconds and timezone) + + $timestring =~ s/:(\d)\s/:$1\0 /; + $timestring =~ s/\s(\d):/ 0$1:/; + $timestring =~ s/\s:/ 00:/; + $timestring =~ s/:\s/:00 /; + $timestring =~ s/\s(\d{2}:\d{2})[\.\s]/ $1:00$2/; + $timestring =~ s/\s(\d{2}:\d{2})$/ $1:00/; + + return $timestring; +} + sub format_time { my ($date, $format, $timezone) = @_; === added directory contrib/mssql_clr_functions --- contrib/mssql_clr_functions 1970-01-01 00:00:00 +0000 +++ contrib/mssql_clr_functions 2010-03-31 23:57:19 +0000 === added directory contrib/mssql_clr_functions/Bugzilla.MSSQL --- contrib/mssql_clr_functions/Bugzilla.MSSQL 1970-01-01 00:00:00 +0000 +++ contrib/mssql_clr_functions/Bugzilla.MSSQL 2010-03-31 23:56:07 +0000 === added file contrib/mssql_clr_functions/Bugzilla.MSSQL/AssemblyInfo.cs --- contrib/mssql_clr_functions/Bugzilla.MSSQL/AssemblyInfo.cs 1970-01-01 00:00:00 +0000 +++ contrib/mssql_clr_functions/Bugzilla.MSSQL/AssemblyInfo.cs 2010-03-31 23:42:55 +0000 @@ -1,0 +1,12 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle("Bugzilla.MSSQL")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.*")] === added file contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.cs --- contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.cs 1970-01-01 00:00:00 +0000 +++ contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.cs 2010-03-31 23:43:33 +0000 @@ -1,0 +1,508 @@ +?// Original Source Zplace.com March 2010 + +using System; +using System.Data; +using System.Data.SqlClient; +using System.Data.SqlTypes; +using Microsoft.SqlServer.Server; +using System.Text.RegularExpressions; +using System.Text; +using System.Collections; +using NaturalComparer; + +public partial class UserDefinedFunctions +{ + + [SqlFunction(Name = "SplitToArrayTable", + FillRowMethodName = "FillRow", TableDefinition = "ID NVARCHAR(MAX)")] + public static IEnumerable SplitToArrayTable([SqlFacet(MaxSize = -1)]SqlString str, SqlChars delimiter) + { + //return single element array if no delimiter is specified + + if (delimiter.Length == 0 ) + return new string[1] { str.Value }; + + //split the string and return a string array + return str.Value.Split(delimiter[0]); + } + + public static void FillRow(object row, out SqlString str) + { + str = new SqlString((string)row); + } + + [Microsoft.SqlServer.Server.SqlFunction()] + public static SqlBoolean REGEXP([SqlFacet(MaxSize = -1)]SqlString subject, [SqlFacet(MaxSize = -1)]SqlString pattern, SqlBoolean ignorecase) + { + if (subject.IsNull || pattern.IsNull) + { + return SqlBoolean.False; + } + + string str_subject = subject.Value; + string str_pattern = pattern.Value; + + if (pattern == "") + { + return SqlBoolean.False; + } + + Boolean test; + if (ignorecase.IsTrue == SqlBoolean.True) + { + test = System.Text.RegularExpressions.Regex.IsMatch(str_subject, str_pattern,RegexOptions.IgnoreCase); + } + else + { + test = System.Text.RegularExpressions.Regex.IsMatch(str_subject, str_pattern); + } + + if (test) + { + return SqlBoolean.True; + } + else + { + return SqlBoolean.False; + } + } + + [Microsoft.SqlServer.Server.SqlFunction()] + public static SqlBoolean NOT_REGEXP([SqlFacet(MaxSize = -1)]SqlString subject, [SqlFacet(MaxSize = -1)]SqlString pattern, SqlBoolean ignorecase) + { + + if (subject.IsNull || pattern.IsNull) + { + return SqlBoolean.False; + } + + string str_subject = subject.Value; + string str_pattern = pattern.Value; + + if (pattern == "") + { + return SqlBoolean.False; + } + + if (REGEXP(str_subject, str_pattern, ignorecase) == SqlBoolean.True) + { + return SqlBoolean.False; + } + else + { + return SqlBoolean.True; + } + } + + [Microsoft.SqlServer.Server.SqlFunction()] + public static SqlInt32 INSTR([SqlFacet(MaxSize = -1)]SqlString find, [SqlFacet(MaxSize = -1)]SqlString inthis, SqlBoolean CaseSensitive) + { + if (find.IsNull || inthis.IsNull) { + return 0; + } + string findthis = find.Value; + string findinthis = inthis.Value; + int ind; + if (CaseSensitive == true) + { + ind = findinthis.IndexOf(findthis, StringComparison.InvariantCulture); + } + else + { + ind = findinthis.IndexOf(findthis, StringComparison.InvariantCultureIgnoreCase); + } + + // Indexof returns 0 if the string is found starting at the first position + // and -1 not found while mysql treats it as one-base so we will emulate this. + if (ind == -1) + { + return 0; + } + else { + return ind+1; + } + + } + + [Microsoft.SqlServer.Server.SqlFunction()] + public static SqlDateTime NOW() + { + + return DateTime.Now; + } + + [Microsoft.SqlServer.Server.SqlFunction()] + public static SqlDateTime LOCALTIMESTAMP() + { + return NOW(); + } + + [Microsoft.SqlServer.Server.SqlFunction()] + public static SqlChars CURRENT_DATE() + { + return DATE_FORMAT(NOW(), "%Y-%m-%d"); + } + + [Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read)] + public static SqlInt32 TO_DAYS(SqlDateTime date) + { + DateTime SubjectDate = date.Value; + DateTime Y1900 = new DateTime(1900, 1, 1); + + long elapsedTicks = SubjectDate.Ticks - Y1900.Ticks; + TimeSpan elapsedSpan = new TimeSpan(elapsedTicks); + return Convert.ToInt32(elapsedSpan.TotalDays); + + } + + [Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read)] + public static SqlDateTime FROM_DAYS(SqlInt32 days) + { + if (days.IsNull) { + return SqlDateTime.Null; + } + DateTime Y1900 = new DateTime(1900, 1, 1); + Y1900.AddDays(days.Value); + return Y1900.AddDays(days.Value); + } + + [Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read)] + public static SqlChars DATE_FORMAT(SqlDateTime SqlDate, SqlString format) + { + if (SqlDate.IsNull) return SqlChars.Null; + string str_format = format.Value; + DateTime Date = SqlDate.Value; + + string a = Date.DayOfWeek.ToString(); + + if (str_format.Contains(@"%a")) + { + str_format = str_format.Replace(@"%a", Date.DayOfWeek.ToString().Substring(0, 3)); + } + + if (str_format.Contains(@"%b")) + { + str_format = str_format.Replace(@"%b", ((ShortMonthOfYear)Date.Month).ToString()); + }; + + if (str_format.Contains(@"%c")) + { + str_format = str_format.Replace(@"%c", Date.Month.ToString()); + } + + if (str_format.Contains(@"%D")) + { + + str_format = str_format.Replace(@"%D", Date.Day.ToString() + GetDateSuffix(Date)); + } + + if (str_format.Contains(@"%d")) + { + str_format = str_format.Replace(@"%d", Date.Day.ToString().PadLeft(2, '0')); + } + + if (str_format.Contains(@"%e")) + { + str_format = str_format.Replace(@"%e", Date.Day.ToString()); + } + + if (str_format.Contains(@"%f")) + { + str_format = str_format.Replace(@"%f", Date.Millisecond.ToString().PadRight(6, '0')); + } + + if (str_format.Contains(@"%H")) + { + str_format = str_format.Replace(@"%H", Date.Hour.ToString().PadLeft(2, '0')); + } + + if (str_format.Contains(@"%h")) + { + str_format = str_format.Replace(@"%h", Date.Hour.ToString().PadLeft(2, '0')); + } + + if (str_format.Contains(@"%I")) + { + str_format = str_format.Replace(@"%I", Date.Hour.ToString().PadLeft(2, '0')); + } + + if (str_format.Contains(@"%i")) + { + str_format = str_format.Replace(@"%i", Date.Minute.ToString().PadLeft(2, '0')); + } + + if (str_format.Contains(@"%j")) + { + str_format = str_format.Replace(@"%j", Date.DayOfYear.ToString().PadLeft(3, '0')); + } + + if (str_format.Contains(@"%k")) + { + str_format = str_format.Replace(@"%k", Date.Hour.ToString().PadLeft(2, '0')); + } + + if (str_format.Contains(@"%l")) + { + str_format = str_format.Replace(@"%l", Date.Hour.ToString()); + } + + if (str_format.Contains(@"%M")) + { + str_format = str_format.Replace(@"%M", ((LongMonthOfYear)Date.Month).ToString()); + } + + if (str_format.Contains(@"%m")) + { + str_format = str_format.Replace(@"%m", Date.Month.ToString().PadLeft(2, '0')); + } + + if (str_format.Contains(@"%p")) + { + string D = @"AM"; + if (Date.Hour >= 12) + { + D = @"PM"; + } + str_format = str_format.Replace(@"%p", D); + } + + if (str_format.Contains(@"%r")) + { + str_format = str_format.Replace(@"%r", Date.TimeOfDay.ToString()); + } + + if (str_format.Contains(@"%S")) + { + str_format = str_format.Replace(@"%s", Date.Second.ToString().PadLeft(2, '0')); + } + + if (str_format.Contains(@"%s")) + { + str_format = str_format.Replace(@"%s", Date.Second.ToString().PadLeft(2, '0')); + } + + if (str_format.Contains(@"%T")) + { + str_format = str_format.Replace(@"%T", Date.ToShortTimeString().ToString()); + } + + if (str_format.Contains(@"%U")) + { + str_format = str_format.Replace(@"%U", WeekOfYear(Date).ToString()); + } + + if (str_format.Contains(@"%u")) + { + str_format = str_format.Replace(@"%u", WeekOfYear(Date).ToString()); + } + + if (str_format.Contains(@"%V")) + { + str_format = str_format.Replace(@"%V", WeekOfYear(Date).ToString()); + } + + if (str_format.Contains(@"%v")) + { + str_format = str_format.Replace(@"%v", WeekOfYear(Date).ToString()); + } + + if (str_format.Contains(@"%W")) + { + str_format = str_format.Replace(@"%W", Date.DayOfWeek.ToString()); + } + + if (str_format.Contains(@"%w")) + { + str_format = str_format.Replace(@"%w", ((LongDayofWeek)Date.DayOfWeek).ToString()); + } + + if (str_format.Contains(@"%X")) + { + str_format = str_format.Replace(@"%X", Date.Year.ToString()); + } + + if (str_format.Contains(@"%x")) + { + str_format = str_format.Replace(@"%x", Date.Year.ToString()); + } + + if (str_format.Contains(@"%Y")) + { + str_format = str_format.Replace(@"%Y", Date.Year.ToString()); + } + + if (str_format.Contains(@"%y")) + { + str_format = str_format.Replace(@"%y", Date.Year.ToString().Substring(2, 2)); + } + + if (str_format.Contains(@"%%")) + { + str_format = str_format.Replace(@"%%", @"%"); + } + + return new SqlChars(str_format.ToString()); + + } + + [Serializable] + private enum LongDayofWeek + { + Sunday = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6 + } + + [Serializable] + private enum ShortMonthOfYear + { + Jan = 1, + Feb = 2, + Mar = 3, + Apr = 4, + May = 5, + Jun = 6, + Jul = 7, + Aug = 8, + Sep = 9, + Oct = 10, + Nov = 11, + Dec = 12 + } + + [Serializable] + private enum LongMonthOfYear + { + January = 1, + February = 2, + March = 3, + April = 4, + May = 5, + June = 6, + July = 7, + August = 8, + September = 9, + October = 10, + November = 11, + December = 12 + } + + private static string GetDateSuffix(SqlDateTime SqlDate) + { + if (SqlDate.IsNull) return null; + + DateTime date = SqlDate.Value; + + string day = date.Day.ToString(); + if (day.EndsWith("1")) + { + return day.StartsWith("1") && date.Day != 1 ? "th" : "st"; + } + else if (day.EndsWith("2")) + { + return day.StartsWith("1") ? "th" : "nd"; + } + else if (day.EndsWith("3")) + { + return day.StartsWith("1") ? "th" : "rd"; + } + return "th"; + } + + private static int WeekOfYear(SqlDateTime SqlDate) + { + if (SqlDate.IsNull) return 0; + + DateTime date = SqlDate.Value; + + System.Globalization.CultureInfo ci = System.Threading.Thread.CurrentThread.CurrentCulture; + System.Globalization.Calendar cal = ci.Calendar; + System.Globalization.CalendarWeekRule cwr = ci.DateTimeFormat.CalendarWeekRule; + DayOfWeek fdow = ci.DateTimeFormat.FirstDayOfWeek; + return cal.GetWeekOfYear(date, cwr, fdow); + } +}; + +[Serializable] +[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(Microsoft.SqlServer.Server.Format.UserDefined, MaxByteSize = -1)] +public struct GROUP_CONCAT : Microsoft.SqlServer.Server.IBinarySerialize +{ + private ArrayList Remembered; + private string Sep; + private string Sort; + public void Init() + { + this.Remembered = new ArrayList(); + this.Sep = null; + this.Sort = null; + } + public void Accumulate([SqlFacet(MaxSize = -1)]SqlString Value, [SqlFacet(MaxSize = -1, IsNullable = true)]SqlString Seperator, SqlString SortOrder) + { + if (Value.IsNull) return; + + // Default a Null to Seperator to an empty string + if (Seperator.IsNull) Seperator = new SqlString(""); + + // Add the Seperator as the first array element + if (this.Remembered.Count == 0) { + this.Remembered.Add(Seperator.Value); + + if (!SortOrder.IsNull && SortOrder.Value.ToUpper() != "ASC" && SortOrder.Value.ToUpper() != "DESC" && SortOrder.Value.ToUpper() != "NTRL") + { + throw new ArgumentException(SortOrder.Value + " is not valid for Sort Order"); + } + if (SortOrder.IsNull) SortOrder = new SqlString("NTRL"); + + this.Remembered.Add(SortOrder.Value.ToUpper()); + } + this.Remembered.Add(Value.Value); + } + + public void Merge(GROUP_CONCAT Group) + { + this.Remembered.AddRange(Group.Remembered.ToArray()); + } + + public SqlString Terminate() + { + // If ASC or DESC then Sort + //new NaturalComparer(NaturalComparerOptions.RomanNumbers)); + if (this.Sort != "NTRL") this.Remembered.Sort(new NaturalComparer.NaturalComparer()); + // If DESC then reverse the Sort order + if (this.Sort == "DESC") this.Remembered.Reverse(); + return new SqlString(string.Join(this.Sep, (string[])this.Remembered.ToArray(typeof(string)))); + } + + public void Read(System.IO.BinaryReader r) + { + int itemCount = r.ReadInt32(); + + this.Remembered = new ArrayList(); + for (int i = 0; i <= itemCount - 1; i++) + { + // Retrieve the Seperator for use by Terminate() + if (this.Sep == null && this.Sort == null) + { + this.Sep = r.ReadString(); + this.Sort = r.ReadString(); + i++; //Extra Increment again since we are reading two lines + } + else + { + this.Remembered.Add(r.ReadString()); + } + } + } + public void Write(System.IO.BinaryWriter w) + { + w.Write(this.Remembered.Count); + foreach (string s in this.Remembered) + { + w.Write(s); + } + } +} === added file contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.csproj --- contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.csproj 1970-01-01 00:00:00 +0000 +++ contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.csproj 2010-03-31 23:45:23 +0000 @@ -1,0 +1,48 @@ +? + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {A8122FF3-7832-4314-92B0-63BBFAC2D32C} + Library + Bugzilla.MSSQL + Bugzilla.MSSQL + v3.5 + + + true + full + false + bin\Debug + DEBUG + prompt + 4 + false + + + none + false + bin\Release + prompt + 4 + false + + + + + + + 3.5 + + + + + + + + + + + \ No newline at end of file === added file contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.pidb Binary files contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.pidb 1970-01-01 00:00:00 +0000 and contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.pidb 2010-01-13 16:47:37 +0000 differ === added file contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.sln --- contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.sln 1970-01-01 00:00:00 +0000 +++ contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.sln 2010-03-31 23:45:23 +0000 @@ -1,0 +1,23 @@ +? +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bugzilla.MSSQL", "Bugzilla.MSSQL.csproj", "{A8122FF3-7832-4314-92B0-63BBFAC2D32C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A8122FF3-7832-4314-92B0-63BBFAC2D32C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8122FF3-7832-4314-92B0-63BBFAC2D32C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8122FF3-7832-4314-92B0-63BBFAC2D32C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8122FF3-7832-4314-92B0-63BBFAC2D32C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = Mockodin.Bugzilla.csproj + EndGlobalSection +EndGlobal === added file contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.suo Binary files contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.suo 1970-01-01 00:00:00 +0000 and contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.suo 2010-03-31 23:50:40 +0000 differ === added file contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.userprefs --- contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.userprefs 1970-01-01 00:00:00 +0000 +++ contrib/mssql_clr_functions/Bugzilla.MSSQL/Bugzilla.MSSQL.userprefs 2010-01-13 16:47:37 +0000 @@ -1,0 +1,21 @@ +? + + + + + + + + + + + + + + + + + + + + \ No newline at end of file === added file contrib/mssql_clr_functions/Bugzilla.MSSQL/NaturalSortComparer.cs --- contrib/mssql_clr_functions/Bugzilla.MSSQL/NaturalSortComparer.cs 1970-01-01 00:00:00 +0000 +++ contrib/mssql_clr_functions/Bugzilla.MSSQL/NaturalSortComparer.cs 2010-03-17 23:34:17 +0000 @@ -1,0 +1,366 @@ +?// Source: http://www.codeproject.com/KB/recipes/NaturalComparer.aspx +// Pascal Ganaye +using System.Globalization; +using System.Collections.Generic; +using System.Collections; + +namespace NaturalComparer +{ + public class NaturalComparer : IComparer, IComparer + { + + private StringParser mParser1; + private StringParser mParser2; + private NaturalComparerOptions mNaturalComparerOptions; + + private enum TokenType + { + Nothing, + Numerical, + String + } + + private class StringParser + { + private TokenType mTokenType; + private string mStringValue; + private decimal mNumericalValue; + private int mIdx; + private string mSource; + private int mLen; + private char mCurChar; + private NaturalComparer mNaturalComparer; + + public StringParser(NaturalComparer naturalComparer) + { + mNaturalComparer = naturalComparer; + } + + public void Init(string source) + { + if (source == null) + source = string.Empty; + mSource = source; + mLen = source.Length; + mIdx = -1; + mNumericalValue = 0; + NextChar(); + NextToken(); + } + + public TokenType TokenType + { + get { return mTokenType; } + } + + public decimal NumericalValue + { + get + { + if (mTokenType == NaturalComparer.TokenType.Numerical) + { + return mNumericalValue; + } + else + { + throw new NaturalComparerException("Internal Error: NumericalValue called on a non numerical value."); + } + } + } + + public string StringValue + { + get { return mStringValue; } + } + + public void NextToken() + { + do + { + //CharUnicodeInfo.GetUnicodeCategory + if (mCurChar == '\0') + { + mTokenType = NaturalComparer.TokenType.Nothing; + mStringValue = null; + return; + } + else if (char.IsDigit(mCurChar)) + { + ParseNumericalValue(); + return; + } + else if (char.IsLetter(mCurChar)) + { + ParseString(); + return; + } + else + { + //ignore this character and loop some more + NextChar(); + } + } + while (true); + } + + private void NextChar() + { + mIdx += 1; + if (mIdx >= mLen) + { + mCurChar = '\0'; + } + else + { + mCurChar = mSource[mIdx]; + } + } + + private void ParseNumericalValue() + { + int start = mIdx; + char NumberDecimalSeparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator[0]; + char NumberGroupSeparator = NumberFormatInfo.CurrentInfo.NumberGroupSeparator[0]; + do + { + NextChar(); + if (mCurChar == NumberDecimalSeparator) + { + // parse digits after the Decimal Separator + do + { + NextChar(); + if (!char.IsDigit(mCurChar) && mCurChar != NumberGroupSeparator) + break; + + } + while (true); + break; + } + else + { + if (!char.IsDigit(mCurChar) && mCurChar != NumberGroupSeparator) + break; + } + } + while (true); + mStringValue = mSource.Substring(start, mIdx - start); + if (decimal.TryParse(mStringValue, out mNumericalValue)) + { + mTokenType = NaturalComparer.TokenType.Numerical; + } + else + { + // We probably have a too long value + mTokenType = NaturalComparer.TokenType.String; + } + } + + private void ParseString() + { + int start = mIdx; + bool roman = (mNaturalComparer.mNaturalComparerOptions & NaturalComparerOptions.RomanNumbers) != 0; + int romanValue = 0; + int lastRoman = int.MaxValue; + int cptLastRoman = 0; + do + { + if (roman) + { + int thisRomanValue = RomanLetterValue(mCurChar); + if (thisRomanValue > 0) + { + bool handled = false; + + if ((thisRomanValue == 1 || thisRomanValue == 10 || thisRomanValue == 100)) + { + NextChar(); + int nextRomanValue = RomanLetterValue(mCurChar); + if (nextRomanValue == thisRomanValue * 10 | nextRomanValue == thisRomanValue * 5) + { + handled = true; + if (nextRomanValue <= lastRoman) + { + romanValue += nextRomanValue - thisRomanValue; + NextChar(); + lastRoman = thisRomanValue / 10; + cptLastRoman = 0; + } + else + { + roman = false; + } + } + } + else + { + NextChar(); + } + if (!handled) + { + if (thisRomanValue <= lastRoman) + { + romanValue += thisRomanValue; + if (lastRoman == thisRomanValue) + { + cptLastRoman += 1; + switch (thisRomanValue) + { + case 1: + case 10: + case 100: + if (cptLastRoman > 4) + roman = false; + + break; + case 5: + case 50: + case 500: + if (cptLastRoman > 1) + roman = false; + + break; + } + } + else + { + lastRoman = thisRomanValue; + cptLastRoman = 1; + } + } + else + { + roman = false; + } + } + } + else + { + roman = false; + } + } + else + { + NextChar(); + } + if (!char.IsLetter(mCurChar)) break; + } + while (true); + mStringValue = mSource.Substring(start, mIdx - start); + if (roman) + { + mNumericalValue = romanValue; + mTokenType = NaturalComparer.TokenType.Numerical; + } + else + { + mTokenType = NaturalComparer.TokenType.String; + } + } + + } + + public NaturalComparer(NaturalComparerOptions NaturalComparerOptions) + { + mNaturalComparerOptions = NaturalComparerOptions; + mParser1 = new StringParser(this); + mParser2 = new StringParser(this); + } + + public NaturalComparer() + : this(NaturalComparerOptions.Default) + { + } + + int System.Collections.Generic.IComparer.Compare(string string1, string string2) + { + mParser1.Init(string1); + mParser2.Init(string2); + int result; + do + { + if (mParser1.TokenType == TokenType.Numerical & mParser2.TokenType == TokenType.Numerical) + { + // both string1 and string2 are numerical + result = decimal.Compare(mParser1.NumericalValue, mParser2.NumericalValue); + } + else + { + result = string.Compare(mParser1.StringValue, mParser2.StringValue); + } + if (result != 0) + { + return result; + } + else + { + mParser1.NextToken(); + mParser2.NextToken(); + } + } + while (!(mParser1.TokenType == TokenType.Nothing & mParser2.TokenType == TokenType.Nothing)); + //identical + return 0; + } + + private static int RomanLetterValue(char c) + { + switch (c) + { + case 'I': + return 1; + case 'V': + return 5; + case 'X': + return 10; + case 'L': + return 50; + case 'C': + return 100; + case 'D': + return 500; + case 'M': + return 1000; + default: + return 0; + } + } + + public int RomanValue(string string1) + { + mParser1.Init(string1); + + if (mParser1.TokenType == TokenType.Numerical) + { + return (int)mParser1.NumericalValue; + } + else + { + return 0; + } + } + + int IComparer.Compare(object x, object y) + { + return ((System.Collections.Generic.IComparer)this).Compare((string)x, (string)y); + } + } + + public class NaturalComparerException : System.Exception + { + + public NaturalComparerException(string msg) + : base(msg) + { + } + } + + [System.Flags()] + public enum NaturalComparerOptions + { + None, + RomanNumbers, + Default = None + } + +} \ No newline at end of file === modified file token.cgi --- token.cgi 2009-10-09 04:31:08 +0000 +++ token.cgi 2010-03-31 23:16:48 +0000 @@ -248,23 +248,33 @@ my $token = shift; my $dbh = Bugzilla->dbh; - # Get the user's ID from the tokens table. - my ($userid, $eventdata) = $dbh->selectrow_array( - q{SELECT userid, eventdata FROM tokens - WHERE token = ?}, undef, $token); - my ($old_email, $new_email) = split(/:/,$eventdata); + my %emails; + + # Get the user's ID, the tokentype and the associated email from the tokens table. + my ($userid, $email) = $dbh->selectrow_array( + q{SELECT userid, eventdata FROM tokens + WHERE token = ?}, undef, $token); + + $emails{emailnew} = $email; + + # Get the old email + $emails{emailold} = $dbh->selectrow_array(qq{SELECT eventdata FROM tokens + WHERE userid = ? AND tokentype = 'emailold'}, undef, $userid); + + # Check the user entered the correct old email address - if(lc($cgi->param('email')) ne lc($old_email)) { + if(lc($cgi->param('email')) ne lc($emails{oldemail})) { + ThrowUserError("email_confirmation_failed"); } # The new email address should be available as this was # confirmed initially so cancel token if it is not still available - if (! is_available_username($new_email,$old_email)) { - $vars->{'email'} = $new_email; # Needed for Bugzilla::Token::Cancel's mail + if (! is_available_username($emails{newemail},$emails{oldemail})) { + $vars->{'email'} = $emails{newemail}; # Needed for Bugzilla::Token::Cancel's mail Bugzilla::Token::Cancel($token, "account_exists", $vars); - ThrowUserError("account_exists", { email => $new_email } ); - } + ThrowUserError("account_exists", { email => $emails{newemail} } ); + } # Update the user's login name in the profiles table and delete the token # from the tokens table. @@ -272,7 +282,7 @@ $dbh->do(q{UPDATE profiles SET login_name = ? WHERE userid = ?}, - undef, ($new_email, $userid)); + undef, ($emails{newemail}, $userid)); $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $token); $dbh->do(q{DELETE FROM tokens WHERE userid = ? AND tokentype = 'emailnew'}, undef, $userid); @@ -300,11 +310,18 @@ $dbh->bz_start_transaction(); - # Get the user's ID from the tokens table. - my ($userid, $tokentype, $eventdata) = $dbh->selectrow_array( - q{SELECT userid, tokentype, eventdata FROM tokens - WHERE token = ?}, undef, $token); - my ($old_email, $new_email) = split(/:/,$eventdata); + # Get the user's ID, the tokentype and the associated email from the tokens table. + my ($userid, $tokentype, $email) = $dbh->selectrow_array( + q{SELECT userid, tokentype, eventdata FROM tokens + WHERE token = ?}, undef, $token); + + my %emails; + $emails{$tokentype} = $email; + + # Get the other email + $emails{$tokentype} = $dbh->selectrow_array(qq{SELECT eventdata FROM tokens + WHERE userid = ? AND tokentype = ?}, undef, $userid, + ($tokentype eq 'emailnew' ? 'emailold' : 'emailnew')); if($tokentype eq "emailold") { $vars->{'message'} = "emailold_change_canceled"; @@ -314,14 +331,14 @@ WHERE userid = ?}, undef, $userid); # check to see if it has been altered - if($actualemail ne $old_email) { + if($actualemail ne $emails{oldemail}) { # XXX - This is NOT safe - if A has change to B, another profile # could have grabbed A's username in the meantime. # The DB constraint will catch this, though $dbh->do(q{UPDATE profiles SET login_name = ? WHERE userid = ?}, - undef, ($old_email, $userid)); + undef, ( $emails{oldemail}, $userid)); # email has changed, so rederive groups @@ -335,8 +352,8 @@ $vars->{'message'} = 'email_change_canceled' } - $vars->{'old_email'} = $old_email; - $vars->{'new_email'} = $new_email; + $vars->{'old_email'} = $emails{oldemail}; + $vars->{'new_email'} = $emails{newemail}; Bugzilla::Token::Cancel($token, $vars->{'message'}, $vars); $dbh->do(q{DELETE FROM tokens WHERE userid = ?