759 lines
24 KiB
Perl
759 lines
24 KiB
Perl
# -*- 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 Oracle Corporation.
|
|
# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
|
|
# All Rights Reserved.
|
|
#
|
|
# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
|
|
# Xiaoou Wu <xiaoou.wu@oracle.com>
|
|
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::DB::Oracle - Bugzilla database compatibility layer for Oracle
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This module overrides methods of the Bugzilla::DB module with Oracle
|
|
specific implementation. It is instantiated by the Bugzilla::DB module
|
|
and should never be used directly.
|
|
|
|
For interface details see L<Bugzilla::DB> and L<DBI>.
|
|
|
|
=cut
|
|
|
|
package Bugzilla::DB::Oracle;
|
|
use strict;
|
|
use base qw(Bugzilla::DB);
|
|
|
|
use DBD::Oracle;
|
|
use DBD::Oracle qw(:ora_types);
|
|
use Bugzilla::Constants;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::Util;
|
|
|
|
#####################################################################
|
|
# Constants
|
|
#####################################################################
|
|
use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
|
|
use constant ISOLATION_LEVEL => 'READ COMMITTED';
|
|
use constant BLOB_TYPE => { ora_type => ORA_BLOB };
|
|
use constant FULLTEXT_OR => ' OR ';
|
|
|
|
sub new {
|
|
my ($class, $params) = @_;
|
|
my ($user, $pass, $host, $dbname, $port) =
|
|
@$params{qw(db_user db_pass db_host db_name db_port)};
|
|
|
|
# You can never connect to Oracle without a DB name,
|
|
# and there is no default DB.
|
|
$dbname ||= Bugzilla->localconfig->{db_name};
|
|
|
|
# Set the language enviroment
|
|
$ENV{'NLS_LANG'} = '.AL32UTF8' if Bugzilla->params->{'utf8'};
|
|
|
|
# construct the DSN from the parameters we got
|
|
my $dsn = "dbi:Oracle:host=$host;sid=$dbname";
|
|
$dsn .= ";port=$port" if $port;
|
|
my $attrs = { FetchHashKeyName => 'NAME_lc',
|
|
LongReadLen => ( Bugzilla->params->{'maxattachmentsize'}
|
|
|| 1000 ) * 1024,
|
|
};
|
|
my $self = $class->db_new({ dsn => $dsn, user => $user,
|
|
pass => $pass, attrs => $attrs });
|
|
# Needed by TheSchwartz
|
|
$self->{private_bz_dsn} = $dsn;
|
|
|
|
bless ($self, $class);
|
|
|
|
# Set the session's default date format to match MySQL
|
|
$self->do("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
|
|
$self->do("ALTER SESSION SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'");
|
|
$self->do("ALTER SESSION SET NLS_LENGTH_SEMANTICS='CHAR'")
|
|
if Bugzilla->params->{'utf8'};
|
|
# To allow case insensitive query.
|
|
$self->do("ALTER SESSION SET NLS_COMP='ANSI'");
|
|
$self->do("ALTER SESSION SET NLS_SORT='BINARY_AI'");
|
|
return $self;
|
|
}
|
|
|
|
sub bz_last_key {
|
|
my ($self, $table, $column) = @_;
|
|
|
|
my $seq = $table . "_" . $column . "_SEQ";
|
|
my ($last_insert_id) = $self->selectrow_array("SELECT $seq.CURRVAL "
|
|
. " FROM DUAL");
|
|
return $last_insert_id;
|
|
}
|
|
|
|
sub bz_check_regexp {
|
|
my ($self, $pattern) = @_;
|
|
|
|
eval { $self->do("SELECT 1 FROM DUAL WHERE "
|
|
. $self->sql_regexp($self->quote("a"), $pattern, 1)) };
|
|
|
|
$@ && ThrowUserError('illegal_regexp',
|
|
{ value => $pattern, dberror => $self->errstr });
|
|
}
|
|
|
|
sub bz_explain {
|
|
my ($self, $sql) = @_;
|
|
my $sth = $self->prepare("EXPLAIN PLAN FOR $sql");
|
|
$sth->execute();
|
|
my $explain = $self->selectcol_arrayref(
|
|
"SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY)");
|
|
return join("\n", @$explain);
|
|
}
|
|
|
|
sub sql_group_concat {
|
|
my ($self, $text, $separator) = @_;
|
|
$separator = $self->quote(', ') if !defined $separator;
|
|
return "group_concat(T_CLOB_DELIM($text, $separator))";
|
|
}
|
|
|
|
sub sql_regexp {
|
|
my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
|
|
$real_pattern ||= $pattern;
|
|
|
|
$self->bz_check_regexp($real_pattern) if !$nocheck;
|
|
|
|
return "REGEXP_LIKE($expr, $pattern)";
|
|
}
|
|
|
|
sub sql_not_regexp {
|
|
my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
|
|
$real_pattern ||= $pattern;
|
|
|
|
$self->bz_check_regexp($real_pattern) if !$nocheck;
|
|
|
|
return "NOT REGEXP_LIKE($expr, $pattern)"
|
|
}
|
|
|
|
sub sql_limit {
|
|
my ($self, $limit, $offset) = @_;
|
|
|
|
if(defined $offset) {
|
|
return "/* LIMIT $limit $offset */";
|
|
}
|
|
return "/* LIMIT $limit */";
|
|
}
|
|
|
|
sub sql_string_concat {
|
|
my ($self, @params) = @_;
|
|
|
|
return 'CONCAT(' . join(', ', @params) . ')';
|
|
}
|
|
|
|
sub sql_string_until {
|
|
my ($self, $string, $substring) = @_;
|
|
return "SUBSTR($string, 1, "
|
|
. $self->sql_position($substring, $string)
|
|
. " - 1)";
|
|
}
|
|
|
|
sub sql_to_days {
|
|
my ($self, $date) = @_;
|
|
|
|
return " TO_CHAR(TO_DATE($date),'J') ";
|
|
}
|
|
sub sql_from_days{
|
|
my ($self, $date) = @_;
|
|
|
|
return " TO_DATE($date,'J') ";
|
|
}
|
|
sub sql_fulltext_search {
|
|
my ($self, $column, $text, $label) = @_;
|
|
$text = $self->quote($text);
|
|
trick_taint($text);
|
|
return "CONTAINS($column,$text,$label)", "SCORE($label)";
|
|
}
|
|
|
|
sub sql_date_format {
|
|
my ($self, $date, $format) = @_;
|
|
|
|
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
|
|
|
$format =~ s/\%Y/YYYY/g;
|
|
$format =~ s/\%y/YY/g;
|
|
$format =~ s/\%m/MM/g;
|
|
$format =~ s/\%d/DD/g;
|
|
$format =~ s/\%a/Dy/g;
|
|
$format =~ s/\%H/HH24/g;
|
|
$format =~ s/\%i/MI/g;
|
|
$format =~ s/\%s/SS/g;
|
|
|
|
return "TO_CHAR($date, " . $self->quote($format) . ")";
|
|
}
|
|
|
|
sub sql_date_math {
|
|
my ($self, $date, $operator, $interval, $units) = @_;
|
|
my $time_sql;
|
|
if ($units =~ /YEAR|MONTH/i) {
|
|
$time_sql = "NUMTOYMINTERVAL($interval,'$units')";
|
|
} else{
|
|
$time_sql = "NUMTODSINTERVAL($interval,'$units')";
|
|
}
|
|
return "$date $operator $time_sql";
|
|
}
|
|
|
|
sub sql_position {
|
|
my ($self, $fragment, $text) = @_;
|
|
return "INSTR($text, $fragment)";
|
|
}
|
|
|
|
sub sql_in {
|
|
my ($self, $column_name, $in_list_ref) = @_;
|
|
my @in_list = @$in_list_ref;
|
|
return $self->SUPER::sql_in($column_name, $in_list_ref) if $#in_list < 1000;
|
|
my @in_str;
|
|
while (@in_list) {
|
|
my $length = $#in_list + 1;
|
|
my $splice = $length > 1000 ? 1000 : $length;
|
|
my @sub_in_list = splice(@in_list, 0, $splice);
|
|
push(@in_str,
|
|
$self->SUPER::sql_in($column_name, \@sub_in_list));
|
|
}
|
|
return "( " . join(" OR ", @in_str) . " )";
|
|
}
|
|
|
|
sub _bz_add_field_table {
|
|
my ($self, $name, $schema_ref, $type) = @_;
|
|
$self->SUPER::_bz_add_field_table($name, $schema_ref);
|
|
if (defined($type) && $type == FIELD_TYPE_MULTI_SELECT) {
|
|
my $uk_name = "UK_" . $self->_bz_schema->_hash_identifier($name . '_value');
|
|
$self->do("ALTER TABLE $name ADD CONSTRAINT $uk_name UNIQUE(value)");
|
|
}
|
|
}
|
|
|
|
sub bz_drop_table {
|
|
my ($self, $name) = @_;
|
|
my $table_exists = $self->bz_table_info($name);
|
|
if ($table_exists) {
|
|
$self->_bz_drop_fks($name);
|
|
$self->SUPER::bz_drop_table($name);
|
|
}
|
|
}
|
|
|
|
# Dropping all FKs for a specified table.
|
|
sub _bz_drop_fks {
|
|
my ($self, $table) = @_;
|
|
my @columns = $self->bz_table_columns($table);
|
|
foreach my $column (@columns) {
|
|
$self->bz_drop_fk($table, $column);
|
|
}
|
|
}
|
|
|
|
sub _fix_empty {
|
|
my ($string) = @_;
|
|
$string = '' if $string eq EMPTY_STRING;
|
|
return $string;
|
|
}
|
|
|
|
sub _fix_arrayref {
|
|
my ($row) = @_;
|
|
return undef if !defined $row;
|
|
foreach my $field (@$row) {
|
|
$field = _fix_empty($field) if defined $field;
|
|
}
|
|
return $row;
|
|
}
|
|
|
|
sub _fix_hashref {
|
|
my ($row) = @_;
|
|
return undef if !defined $row;
|
|
foreach my $value (values %$row) {
|
|
$value = _fix_empty($value) if defined $value;
|
|
}
|
|
return $row;
|
|
}
|
|
|
|
sub adjust_statement {
|
|
my ($sql) = @_;
|
|
|
|
if ($sql =~ /^CREATE OR REPLACE.*/i){
|
|
return $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;
|
|
|
|
# Oracle requires a FROM clause in all SELECT statements, so append
|
|
# "FROM dual" to queries without one (e.g., "SELECT NOW()")
|
|
my $is_select = ($part =~ m/^\s*SELECT\b/io);
|
|
my $has_from = ($part =~ m/\bFROM\b/io) if $is_select;
|
|
|
|
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
|
|
$part =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
|
|
|
|
# Oracle use SUBSTR instead of SUBSTRING
|
|
$part =~ s/\bSUBSTRING\b/SUBSTR/io;
|
|
|
|
# Oracle need no 'AS'
|
|
$part =~ s/\bAS\b//ig;
|
|
|
|
# Oracle 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
|
|
$has_from = ($nonstring =~ m/\bFROM\b/io)
|
|
if ($is_select and !$has_from);
|
|
|
|
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
|
|
$nonstring =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
|
|
|
|
# Oracle use SUBSTR instead of SUBSTRING
|
|
$nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
|
|
|
|
# Oracle need no 'AS'
|
|
$nonstring =~ s/\bAS\b//ig;
|
|
|
|
# Take the first 4000 chars for comparison
|
|
$nonstring =~ s/\(\s*(longdescs_\d+\.thetext|attachdata_\d+\.thedata)/
|
|
\(DBMS_LOB.SUBSTR\($1, 4000, 1\)/ig;
|
|
|
|
# Look for a LIMIT clause
|
|
($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
|
|
|
|
if(!length($string)){
|
|
push @result, EMPTY_STRING;
|
|
push @result, $nonstring;
|
|
} else {
|
|
push @result, $string;
|
|
push @result, $nonstring;
|
|
}
|
|
}
|
|
|
|
my $new_sql = join "'", @result;
|
|
|
|
# Append "FROM dual" if this is a SELECT without a FROM clause
|
|
$new_sql .= " FROM DUAL" if ($is_select and !$has_from);
|
|
|
|
# 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);
|
|
$before_where = "$before_from FROM ($before_from,"
|
|
. " ROW_NUMBER() OVER (ORDER BY 1) R "
|
|
. " FROM $after_from ) ";
|
|
$after_where = " R BETWEEN $offset+1 AND $limit+$offset";
|
|
}
|
|
} else {
|
|
$after_where = " rownum <=$limit AND ".$after_where;
|
|
}
|
|
|
|
$new_sql = $before_where." WHERE ".$after_where;
|
|
}
|
|
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(@_);
|
|
_fix_arrayref(\@row);
|
|
return @row;
|
|
} else {
|
|
my $row = $self->SUPER::selectrow_array(@_);
|
|
$row = _fix_empty($row) if defined $row;
|
|
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;
|
|
|
|
_fix_arrayref($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;
|
|
|
|
_fix_hashref($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;
|
|
|
|
foreach my $row (@$ref) {
|
|
if (ref($row) eq 'ARRAY') {
|
|
_fix_arrayref($row);
|
|
}
|
|
elsif (ref($row) eq 'HASH') {
|
|
_fix_hashref($row);
|
|
}
|
|
}
|
|
|
|
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;
|
|
foreach my $row (values %$rows) {
|
|
_fix_hashref($row);
|
|
}
|
|
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;
|
|
_fix_arrayref($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::Oracle::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::Oracle::st';
|
|
}
|
|
|
|
sub quote_identifier {
|
|
my ($self,$id) = @_;
|
|
return $id;
|
|
}
|
|
|
|
#####################################################################
|
|
# Protected "Real Database" Schema Information Methods
|
|
#####################################################################
|
|
|
|
sub bz_table_columns_real {
|
|
my ($self, $table) = @_;
|
|
$table = uc($table);
|
|
my $cols = $self->selectcol_arrayref(
|
|
"SELECT LOWER(COLUMN_NAME) FROM USER_TAB_COLUMNS WHERE
|
|
TABLE_NAME = ? ORDER BY COLUMN_NAME", undef, $table);
|
|
return @$cols;
|
|
}
|
|
|
|
sub bz_table_list_real {
|
|
my ($self) = @_;
|
|
my $tables = $self->selectcol_arrayref(
|
|
"SELECT LOWER(TABLE_NAME) FROM USER_TABLES WHERE
|
|
TABLE_NAME NOT LIKE ? ORDER BY TABLE_NAME", undef, 'DR$%');
|
|
return @$tables;
|
|
}
|
|
|
|
#####################################################################
|
|
# Custom Database Setup
|
|
#####################################################################
|
|
|
|
sub bz_setup_database {
|
|
my $self = shift;
|
|
|
|
# Create a function that returns SYSDATE to emulate MySQL's "NOW()".
|
|
# Function NOW() is used widely in Bugzilla SQLs, but Oracle does not
|
|
# have that function, So we have to create one ourself.
|
|
$self->do("CREATE OR REPLACE FUNCTION NOW "
|
|
. " RETURN DATE IS BEGIN RETURN SYSDATE; END;");
|
|
$self->do("CREATE OR REPLACE FUNCTION CHAR_LENGTH(COLUMN_NAME VARCHAR2)"
|
|
. " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
|
|
|
|
# Create types for group_concat
|
|
my $t_clob_delim = $self->selectcol_arrayref("
|
|
SELECT TYPE_NAME FROM USER_TYPES WHERE TYPE_NAME=?",
|
|
undef, 'T_CLOB_DELIM');
|
|
|
|
if ( !@$t_clob_delim ) {
|
|
$self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
|
|
. "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256));");
|
|
}
|
|
|
|
$self->do("CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT
|
|
( CLOB_CONTENT CLOB,
|
|
DELIMITER VARCHAR2(256),
|
|
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
|
|
SCTX IN OUT NOCOPY T_GROUP_CONCAT)
|
|
RETURN NUMBER,
|
|
MEMBER FUNCTION ODCIAGGREGATEITERATE(
|
|
SELF IN OUT NOCOPY T_GROUP_CONCAT,
|
|
VALUE IN T_CLOB_DELIM)
|
|
RETURN NUMBER,
|
|
MEMBER FUNCTION ODCIAGGREGATETERMINATE(
|
|
SELF IN T_GROUP_CONCAT,
|
|
RETURNVALUE OUT NOCOPY CLOB,
|
|
FLAGS IN NUMBER)
|
|
RETURN NUMBER,
|
|
MEMBER FUNCTION ODCIAGGREGATEMERGE(
|
|
SELF IN OUT NOCOPY T_GROUP_CONCAT,
|
|
CTX2 IN T_GROUP_CONCAT)
|
|
RETURN NUMBER);");
|
|
|
|
$self->do("CREATE OR REPLACE TYPE BODY T_GROUP_CONCAT IS
|
|
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(
|
|
SCTX IN OUT NOCOPY T_GROUP_CONCAT)
|
|
RETURN NUMBER IS
|
|
BEGIN
|
|
SCTX := T_GROUP_CONCAT(EMPTY_CLOB(), NULL);
|
|
DBMS_LOB.CREATETEMPORARY(SCTX.CLOB_CONTENT, TRUE);
|
|
RETURN ODCICONST.SUCCESS;
|
|
END;
|
|
MEMBER FUNCTION ODCIAGGREGATEITERATE(
|
|
SELF IN OUT NOCOPY T_GROUP_CONCAT,
|
|
VALUE IN T_CLOB_DELIM)
|
|
RETURN NUMBER IS
|
|
BEGIN
|
|
SELF.DELIMITER := VALUE.P_DELIMITER;
|
|
DBMS_LOB.WRITEAPPEND(SELF.CLOB_CONTENT,
|
|
LENGTH(SELF.DELIMITER),
|
|
SELF.DELIMITER);
|
|
DBMS_LOB.APPEND(SELF.CLOB_CONTENT, VALUE.P_CONTENT);
|
|
|
|
RETURN ODCICONST.SUCCESS;
|
|
END;
|
|
MEMBER FUNCTION ODCIAGGREGATETERMINATE(
|
|
SELF IN T_GROUP_CONCAT,
|
|
RETURNVALUE OUT NOCOPY CLOB,
|
|
FLAGS IN NUMBER)
|
|
RETURN NUMBER IS
|
|
BEGIN
|
|
RETURNVALUE := RTRIM(LTRIM(SELF.CLOB_CONTENT,
|
|
SELF.DELIMITER),
|
|
SELF.DELIMITER);
|
|
RETURN ODCICONST.SUCCESS;
|
|
END;
|
|
MEMBER FUNCTION ODCIAGGREGATEMERGE(
|
|
SELF IN OUT NOCOPY T_GROUP_CONCAT,
|
|
CTX2 IN T_GROUP_CONCAT)
|
|
RETURN NUMBER IS
|
|
BEGIN
|
|
DBMS_LOB.WRITEAPPEND(SELF.CLOB_CONTENT,
|
|
LENGTH(SELF.DELIMITER),
|
|
SELF.DELIMITER);
|
|
DBMS_LOB.APPEND(SELF.CLOB_CONTENT, CTX2.CLOB_CONTENT);
|
|
RETURN ODCICONST.SUCCESS;
|
|
END;
|
|
END;");
|
|
|
|
# Create user-defined aggregate function group_concat
|
|
$self->do("CREATE OR REPLACE FUNCTION GROUP_CONCAT(P_INPUT T_CLOB_DELIM)
|
|
RETURN CLOB
|
|
DETERMINISTIC PARALLEL_ENABLE AGGREGATE USING T_GROUP_CONCAT;");
|
|
|
|
# Create a WORLD_LEXER named BZ_LEX for multilingual fulltext search
|
|
my $lexer = $self->selectcol_arrayref(
|
|
"SELECT pre_name FROM CTXSYS.CTX_PREFERENCES WHERE pre_name = ? AND
|
|
pre_owner = ?",
|
|
undef,'BZ_LEX',uc(Bugzilla->localconfig->{db_user}));
|
|
if(!@$lexer) {
|
|
$self->do("BEGIN CTX_DDL.CREATE_PREFERENCE
|
|
('BZ_LEX', 'WORLD_LEXER'); END;");
|
|
}
|
|
|
|
$self->SUPER::bz_setup_database(@_);
|
|
|
|
my @tables = $self->bz_table_list_real();
|
|
foreach my $table (@tables) {
|
|
my @columns = $self->bz_table_columns_real($table);
|
|
foreach my $column (@columns) {
|
|
my $def = $self->bz_column_info($table, $column);
|
|
if ($def->{REFERENCES}) {
|
|
my $references = $def->{REFERENCES};
|
|
my $update = $references->{UPDATE} || 'CASCADE';
|
|
my $to_table = $references->{TABLE};
|
|
my $to_column = $references->{COLUMN};
|
|
my $fk_name = $self->_bz_schema->_get_fk_name($table,
|
|
$column,
|
|
$references);
|
|
if ( $update =~ /CASCADE/i ){
|
|
my $trigger_name = uc($fk_name . "_UC");
|
|
my $exist_trigger = $self->selectcol_arrayref(
|
|
"SELECT OBJECT_NAME FROM USER_OBJECTS
|
|
WHERE OBJECT_NAME = ?", undef, $trigger_name);
|
|
if(@$exist_trigger) {
|
|
$self->do("DROP TRIGGER $trigger_name");
|
|
}
|
|
|
|
my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
|
|
. " AFTER UPDATE OF $to_column ON $to_table "
|
|
. " REFERENCING "
|
|
. " NEW AS NEW "
|
|
. " OLD AS OLD "
|
|
. " FOR EACH ROW "
|
|
. " BEGIN "
|
|
. " UPDATE $table"
|
|
. " SET $column = :NEW.$to_column"
|
|
. " WHERE $column = :OLD.$to_column;"
|
|
. " END $trigger_name;";
|
|
$self->do($tr_str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Drop the trigger which causes bug 541553
|
|
my $trigger_name = "PRODUCTS_MILESTONEURL";
|
|
my $exist_trigger = $self->selectcol_arrayref(
|
|
"SELECT OBJECT_NAME FROM USER_OBJECTS
|
|
WHERE OBJECT_NAME = ?", undef, $trigger_name);
|
|
if(@$exist_trigger) {
|
|
$self->do("DROP TRIGGER $trigger_name");
|
|
}
|
|
}
|
|
|
|
package Bugzilla::DB::Oracle::st;
|
|
use base qw(DBI::st);
|
|
|
|
sub fetchrow_arrayref {
|
|
my $self = shift;
|
|
my $ref = $self->SUPER::fetchrow_arrayref(@_);
|
|
return undef if !defined $ref;
|
|
Bugzilla::DB::Oracle::_fix_arrayref($ref);
|
|
return $ref;
|
|
}
|
|
|
|
sub fetchrow_array {
|
|
my $self = shift;
|
|
if ( wantarray ) {
|
|
my @row = $self->SUPER::fetchrow_array(@_);
|
|
Bugzilla::DB::Oracle::_fix_arrayref(\@row);
|
|
return @row;
|
|
} else {
|
|
my $row = $self->SUPER::fetchrow_array(@_);
|
|
$row = Bugzilla::DB::Oracle::_fix_empty($row) if defined $row;
|
|
return $row;
|
|
}
|
|
}
|
|
|
|
sub fetchrow_hashref {
|
|
my $self = shift;
|
|
my $ref = $self->SUPER::fetchrow_hashref(@_);
|
|
return undef if !defined $ref;
|
|
Bugzilla::DB::Oracle::_fix_hashref($ref);
|
|
return $ref;
|
|
}
|
|
|
|
sub fetchall_arrayref {
|
|
my $self = shift;
|
|
my $ref = $self->SUPER::fetchall_arrayref(@_);
|
|
return undef if !defined $ref;
|
|
foreach my $row (@$ref) {
|
|
if (ref($row) eq 'ARRAY') {
|
|
Bugzilla::DB::Oracle::_fix_arrayref($row);
|
|
}
|
|
elsif (ref($row) eq 'HASH') {
|
|
Bugzilla::DB::Oracle::_fix_hashref($row);
|
|
}
|
|
}
|
|
return $ref;
|
|
}
|
|
|
|
sub fetchall_hashref {
|
|
my $self = shift;
|
|
my $ref = $self->SUPER::fetchall_hashref(@_);
|
|
return undef if !defined $ref;
|
|
foreach my $row (values %$ref) {
|
|
Bugzilla::DB::Oracle::_fix_hashref($row);
|
|
}
|
|
return $ref;
|
|
}
|
|
|
|
sub fetch {
|
|
my $self = shift;
|
|
my $row = $self->SUPER::fetch(@_);
|
|
if ($row) {
|
|
Bugzilla::DB::Oracle::_fix_arrayref($row);
|
|
}
|
|
return $row;
|
|
}
|
|
1;
|