From 0b740b2896e314ea4237600a075bf972faf7bca2 Mon Sep 17 00:00:00 2001 From: Pami Ketolainen Date: Thu, 11 Apr 2013 15:18:07 +0200 Subject: Bug 860723: Custom fields are shown twice in report axis selectors r/a=LpSolit --- query.cgi | 7 ------- template/en/default/search/search-report-select.html.tmpl | 7 ------- 2 files changed, 14 deletions(-) diff --git a/query.cgi b/query.cgi index bfb79e52c..26c26049d 100755 --- a/query.cgi +++ b/query.cgi @@ -248,13 +248,6 @@ if (($cgi->param('query_format') || $cgi->param('format') || "") $vars->{'category'} = Bugzilla::Chart::getVisibleSeries(); } -if ($cgi->param('format') && $cgi->param('format') =~ /^report-(table|graph)$/) { - # Get legal custom fields for tabular and graphical reports. - my @custom_fields_for_reports = - grep { $_->type == FIELD_TYPE_SINGLE_SELECT } Bugzilla->active_custom_fields; - $vars->{'custom_fields'} = \@custom_fields_for_reports; -} - $vars->{'known_name'} = $cgi->param('known_name'); $vars->{'columnlist'} = $cgi->param('columnlist'); diff --git a/template/en/default/search/search-report-select.html.tmpl b/template/en/default/search/search-report-select.html.tmpl index 5e5db06e2..44425898a 100644 --- a/template/en/default/search/search-report-select.html.tmpl +++ b/template/en/default/search/search-report-select.html.tmpl @@ -39,12 +39,5 @@ [% " selected" IF default.$name.0 == field %]> [% field_descs.$field || field FILTER html %] [% END %] - - [%# Single-select fields are also valid column names. %] - [% FOREACH field = custom_fields %] - - [% END %] [% END %] -- cgit v1.2.3-24-g4f1b From 6b393ecc304b3c239eb0399b53e0c69b918b2334 Mon Sep 17 00:00:00 2001 From: Frédéric Buclin Date: Mon, 15 Apr 2013 23:27:10 +0200 Subject: Bug 861528: $user->can_enter_product() now returns the product object instead of 1 r=glob a=LpSolit --- Bugzilla/User.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 1bd101a92..878daea60 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -2428,7 +2428,8 @@ the database again. Used mostly by L. =item C - Description: Returns 1 if the user can enter bugs into the specified product. + Description: Returns a product object if the user can enter bugs into the + specified product. If the user cannot enter bugs into the product, the behavior of this method depends on the value of $warn: - if $warn is false (or not given), a 'false' value is returned; @@ -2439,7 +2440,7 @@ the database again. Used mostly by L. must be thrown if the user cannot enter bugs into the specified product. - Returns: 1 if the user can enter bugs into the product, + Returns: A product object if the user can enter bugs into the product, 0 if the user cannot enter bugs into the product and if $warn is false (an error is thrown if $warn is true). -- cgit v1.2.3-24-g4f1b From e6c6efd5b30f184ac22410808b3bb263eb00779a Mon Sep 17 00:00:00 2001 From: Pami Ketolainen Date: Tue, 16 Apr 2013 12:14:23 +0200 Subject: Bug 782210: If a custom field depends on a product, component or classification, the "mandatory" bit is ignored on bug creation r/a=LpSolit --- Bugzilla/Bug.pm | 20 ++++++++++++-------- Bugzilla/Field/ChoiceInterface.pm | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index cad024bbd..7b86ab2a1 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -730,6 +730,17 @@ sub run_create_validators { my $class = shift; my $params = $class->SUPER::run_create_validators(@_); + # Add classification for checking mandatory fields which depend on it + $params->{classification} = $params->{product}->classification->name; + + my @mandatory_fields = @{ Bugzilla->fields({ is_mandatory => 1, + enter_bug => 1, + obsolete => 0 }) }; + foreach my $field (@mandatory_fields) { + $class->_check_field_is_mandatory($params->{$field->name}, $field, + $params); + } + my $product = delete $params->{product}; $params->{product_id} = $product->id; my $component = delete $params->{component}; @@ -754,18 +765,11 @@ sub run_create_validators { delete $params->{resolution}; delete $params->{lastdiffed}; delete $params->{bug_id}; + delete $params->{classification}; Bugzilla::Hook::process('bug_end_of_create_validators', { params => $params }); - my @mandatory_fields = @{ Bugzilla->fields({ is_mandatory => 1, - enter_bug => 1, - obsolete => 0 }) }; - foreach my $field (@mandatory_fields) { - $class->_check_field_is_mandatory($params->{$field->name}, $field, - $params); - } - return $params; } diff --git a/Bugzilla/Field/ChoiceInterface.pm b/Bugzilla/Field/ChoiceInterface.pm index 87354a103..3292536d3 100644 --- a/Bugzilla/Field/ChoiceInterface.pm +++ b/Bugzilla/Field/ChoiceInterface.pm @@ -183,6 +183,7 @@ sub is_set_on_bug { # This allows bug/create/create.html.tmpl to pass in a hashref that # looks like a bug object. my $value = blessed($bug) ? $bug->$field_name : $bug->{$field_name}; + $value = $value->name if blessed($value); return 0 if !defined $value; if ($self->field->type == FIELD_TYPE_BUG_URLS -- cgit v1.2.3-24-g4f1b From 27d9af24e9dbcb0facbd83d2133013d14ed19ebb Mon Sep 17 00:00:00 2001 From: Byron Jones Date: Thu, 18 Apr 2013 01:18:03 +0800 Subject: Bug 828344: Make "contains all of the words" look for all words within the same comment or flag r=LpSolit, a=LpSolit --- Bugzilla/Search.pm | 132 ++++++++++++++++++++++++++++++++++++----- Bugzilla/Search/Clause.pm | 36 ++++++----- Bugzilla/Search/Quicksearch.pm | 4 +- 3 files changed, 138 insertions(+), 34 deletions(-) diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index 656d163ea..f65de801d 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -290,12 +290,14 @@ use constant OPERATOR_FIELD_OVERRIDE => { }, dependson => MULTI_SELECT_OVERRIDE, keywords => MULTI_SELECT_OVERRIDE, - 'flagtypes.name' => MULTI_SELECT_OVERRIDE, + 'flagtypes.name' => { + _non_changed => \&_flagtypes_nonchanged, + }, longdesc => { - %{ MULTI_SELECT_OVERRIDE() }, changedby => \&_long_desc_changedby, changedbefore => \&_long_desc_changedbefore_after, changedafter => \&_long_desc_changedbefore_after, + _non_changed => \&_long_desc_nonchanged, }, 'longdescs.count' => { changedby => \&_long_desc_changedby, @@ -690,8 +692,16 @@ sub sql { my ($self) = @_; return $self->{sql} if $self->{sql}; my $dbh = Bugzilla->dbh; - + my ($joins, $clause) = $self->_charts_to_conditions(); + + if (!$clause->as_string + && !Bugzilla->params->{'search_allow_no_criteria'} + && !$self->{allow_unlimited}) + { + ThrowUserError('buglist_parameters_required'); + } + my $select = join(', ', $self->_sql_select); my $from = $self->_sql_from($joins); my $where = $self->_sql_where($clause); @@ -1191,14 +1201,7 @@ sub _sql_where { # SQL a bit more readable for debugging. my $where = join("\n AND ", $self->_standard_where); my $clause_sql = $main_clause->as_string; - if ($clause_sql) { - $where .= "\n AND " . $clause_sql; - } - elsif (!Bugzilla->params->{'search_allow_no_criteria'} - && !$self->{allow_unlimited}) - { - ThrowUserError('buglist_parameters_required'); - } + $where .= "\n AND " . $clause_sql if $clause_sql; return $where; } @@ -1861,8 +1864,13 @@ sub _quote_unless_numeric { } sub build_subselect { - my ($outer, $inner, $table, $cond) = @_; - return "$outer IN (SELECT $inner FROM $table WHERE $cond)"; + # Execute subselects immediately to avoid dependent subqueries, which are + # large performance hits on MySql + my $q = "SELECT DISTINCT $inner FROM $table WHERE $cond"; + my $dbh = Bugzilla->dbh; + my $list = $dbh->selectcol_arrayref($q); + return $negate ? "1=1" : "1=2" unless @$list; + return $dbh->sql_in($outer, $list, $negate); } # Used by anyexact to get the list of input values. This allows us to @@ -2327,14 +2335,51 @@ sub _long_desc_changedbefore_after { } } +sub _long_desc_nonchanged { + my ($self, $args) = @_; + my ($chart_id, $operator, $value, $joins, $bugs_table) = + @$args{qw(chart_id operator value joins bugs_table)}; + my $dbh = Bugzilla->dbh; + + my $table = "longdescs_$chart_id"; + my $join_args = { + chart_id => $chart_id, + sequence => $chart_id, + field => 'longdesc', + full_field => "$table.thetext", + operator => $operator, + value => $value, + all_values => $value, + quoted => $dbh->quote($value), + joins => [], + bugs_table => $bugs_table, + }; + $self->_do_operator_function($join_args); + + # If the user is not part of the insiders group, they cannot see + # private comments + if (!$self->_user->is_insider) { + $join_args->{term} .= " AND $table.isprivate = 0"; + } + + my $join = { + table => 'longdescs', + as => $table, + extra => [ $join_args->{term} ], + }; + push(@$joins, $join); + + $args->{term} = "$table.comment_id IS NOT NULL"; +} + sub _content_matches { my ($self, $args) = @_; my ($chart_id, $joins, $fields, $operator, $value) = @$args{qw(chart_id joins fields operator value)}; my $dbh = Bugzilla->dbh; - + # "content" is an alias for columns containing text for which we - # can search a full-text index and retrieve results by relevance, + # can search a full-text index and retrieve results by relevance, # currently just bug comments (and summaries to some degree). # There's only one way to search a full-text index, so we only # accept the "matches" operator, which is specific to full-text @@ -2582,6 +2627,63 @@ sub _multiselect_multiple { } } +sub _flagtypes_nonchanged { + my ($self, $args) = @_; + my ($chart_id, $operator, $value, $joins, $bugs_table) = + @$args{qw(chart_id operator value joins bugs_table)}; + my $dbh = Bugzilla->dbh; + my $join; + + # join to the attachments table + my $attach_table = "attachments_$chart_id"; + $join = { + table => 'attachments', + as => $attach_table, + from => "$bugs_table.bug_id", + to => "bug_id", + extra => [ ($self->_user->is_insider ? '' : "$attach_table.isprivate = 0") ], + }; + push(@$joins, $join); + + # join to the flags table + my $flags_table = "flags_$chart_id"; + $join = { + table => 'flags', + as => $flags_table, + from => "$bugs_table.bug_id", + to => "bug_id", + extra => [ "($flags_table.attach_id = $attach_table.attach_id " . + " OR $flags_table.attach_id IS NULL)" ], + }; + push(@$joins, $join); + + # join to the flagtypes table + my $flagtypes_table = "flagtypes_$chart_id"; + $join = { + table => 'flagtypes', + as => $flagtypes_table, + from => "$flags_table.type_id", + to => "id", + }; + push(@$joins, $join); + + # join to the profiles table for the requestee + my $flag_profile_table = "flag_profiles_$chart_id"; + $join = { + table => 'profiles', + as => $flag_profile_table, + from => "$flags_table.requestee_id", + to => "userid", + }; + push(@$joins, $join); + + $args->{full_field} = $dbh->sql_string_concat("$flagtypes_table.name", + "$flags_table.status", + "COALESCE($flag_profile_table.login_name, '')"); + + $self->_do_operator_function($args); +} + sub _multiselect_nonchanged { my ($self, $args) = @_; my ($chart_id, $joins, $field, $operator) = diff --git a/Bugzilla/Search/Clause.pm b/Bugzilla/Search/Clause.pm index a068ce5ed..5f5ea5b50 100644 --- a/Bugzilla/Search/Clause.pm +++ b/Bugzilla/Search/Clause.pm @@ -93,25 +93,29 @@ sub walk_conditions { sub as_string { my ($self) = @_; - my @strings; - foreach my $child (@{ $self->children }) { - next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions; - next if $child->isa('Bugzilla::Search::Condition') - && !$child->translated; + if (!$self->{sql}) { + my @strings; + foreach my $child (@{ $self->children }) { + next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions; + next if $child->isa('Bugzilla::Search::Condition') + && !$child->translated; - my $string = $child->as_string; - if ($self->joiner eq 'AND') { - $string = "( $string )" if $string =~ /OR/; - } - else { - $string = "( $string )" if $string =~ /AND/; + my $string = $child->as_string; + next unless $string; + if ($self->joiner eq 'AND') { + $string = "( $string )" if $string =~ /OR/; + } + else { + $string = "( $string )" if $string =~ /AND/; + } + push(@strings, $string); } - push(@strings, $string); + + my $sql = join(' ' . $self->joiner . ' ', @strings); + $sql = "NOT( $sql )" if $sql && $self->negate; + $self->{sql} = $sql; } - - my $sql = join(' ' . $self->joiner . ' ', @strings); - $sql = "NOT( $sql )" if $sql && $self->negate; - return $sql; + return $self->{sql}; } # Search.pm converts URL parameters to Clause objects. This helps do the diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm index 7424f831f..a5144d0e0 100644 --- a/Bugzilla/Search/Quicksearch.pm +++ b/Bugzilla/Search/Quicksearch.pm @@ -376,10 +376,8 @@ sub _handle_field_names { my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_; # Flag and requestee shortcut - if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) { + if ($or_operand =~ /^(?:flag:)?([^\?]+\?[^\?]*)$/) { addChart('flagtypes.name', 'substring', $1, $negate); - $chart++; $and = $or = 0; # Next chart for boolean AND - addChart('requestees.login_name', 'substring', $2, $negate); return 1; } -- cgit v1.2.3-24-g4f1b From 8d3676154f746a5ec4f3234d5a12656c222e37ff Mon Sep 17 00:00:00 2001 From: Byron Jones Date: Thu, 18 Apr 2013 01:38:22 +0800 Subject: revert commit for bug 828344 --- Bugzilla/Search.pm | 132 +++++------------------------------------ Bugzilla/Search/Clause.pm | 36 +++++------ Bugzilla/Search/Quicksearch.pm | 4 +- 3 files changed, 34 insertions(+), 138 deletions(-) diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index f65de801d..656d163ea 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -290,14 +290,12 @@ use constant OPERATOR_FIELD_OVERRIDE => { }, dependson => MULTI_SELECT_OVERRIDE, keywords => MULTI_SELECT_OVERRIDE, - 'flagtypes.name' => { - _non_changed => \&_flagtypes_nonchanged, - }, + 'flagtypes.name' => MULTI_SELECT_OVERRIDE, longdesc => { + %{ MULTI_SELECT_OVERRIDE() }, changedby => \&_long_desc_changedby, changedbefore => \&_long_desc_changedbefore_after, changedafter => \&_long_desc_changedbefore_after, - _non_changed => \&_long_desc_nonchanged, }, 'longdescs.count' => { changedby => \&_long_desc_changedby, @@ -692,16 +690,8 @@ sub sql { my ($self) = @_; return $self->{sql} if $self->{sql}; my $dbh = Bugzilla->dbh; - + my ($joins, $clause) = $self->_charts_to_conditions(); - - if (!$clause->as_string - && !Bugzilla->params->{'search_allow_no_criteria'} - && !$self->{allow_unlimited}) - { - ThrowUserError('buglist_parameters_required'); - } - my $select = join(', ', $self->_sql_select); my $from = $self->_sql_from($joins); my $where = $self->_sql_where($clause); @@ -1201,7 +1191,14 @@ sub _sql_where { # SQL a bit more readable for debugging. my $where = join("\n AND ", $self->_standard_where); my $clause_sql = $main_clause->as_string; - $where .= "\n AND " . $clause_sql if $clause_sql; + if ($clause_sql) { + $where .= "\n AND " . $clause_sql; + } + elsif (!Bugzilla->params->{'search_allow_no_criteria'} + && !$self->{allow_unlimited}) + { + ThrowUserError('buglist_parameters_required'); + } return $where; } @@ -1864,13 +1861,8 @@ sub _quote_unless_numeric { } sub build_subselect { - # Execute subselects immediately to avoid dependent subqueries, which are - # large performance hits on MySql - my $q = "SELECT DISTINCT $inner FROM $table WHERE $cond"; - my $dbh = Bugzilla->dbh; - my $list = $dbh->selectcol_arrayref($q); - return $negate ? "1=1" : "1=2" unless @$list; - return $dbh->sql_in($outer, $list, $negate); + my ($outer, $inner, $table, $cond) = @_; + return "$outer IN (SELECT $inner FROM $table WHERE $cond)"; } # Used by anyexact to get the list of input values. This allows us to @@ -2335,51 +2327,14 @@ sub _long_desc_changedbefore_after { } } -sub _long_desc_nonchanged { - my ($self, $args) = @_; - my ($chart_id, $operator, $value, $joins, $bugs_table) = - @$args{qw(chart_id operator value joins bugs_table)}; - my $dbh = Bugzilla->dbh; - - my $table = "longdescs_$chart_id"; - my $join_args = { - chart_id => $chart_id, - sequence => $chart_id, - field => 'longdesc', - full_field => "$table.thetext", - operator => $operator, - value => $value, - all_values => $value, - quoted => $dbh->quote($value), - joins => [], - bugs_table => $bugs_table, - }; - $self->_do_operator_function($join_args); - - # If the user is not part of the insiders group, they cannot see - # private comments - if (!$self->_user->is_insider) { - $join_args->{term} .= " AND $table.isprivate = 0"; - } - - my $join = { - table => 'longdescs', - as => $table, - extra => [ $join_args->{term} ], - }; - push(@$joins, $join); - - $args->{term} = "$table.comment_id IS NOT NULL"; -} - sub _content_matches { my ($self, $args) = @_; my ($chart_id, $joins, $fields, $operator, $value) = @$args{qw(chart_id joins fields operator value)}; my $dbh = Bugzilla->dbh; - + # "content" is an alias for columns containing text for which we - # can search a full-text index and retrieve results by relevance, + # can search a full-text index and retrieve results by relevance, # currently just bug comments (and summaries to some degree). # There's only one way to search a full-text index, so we only # accept the "matches" operator, which is specific to full-text @@ -2627,63 +2582,6 @@ sub _multiselect_multiple { } } -sub _flagtypes_nonchanged { - my ($self, $args) = @_; - my ($chart_id, $operator, $value, $joins, $bugs_table) = - @$args{qw(chart_id operator value joins bugs_table)}; - my $dbh = Bugzilla->dbh; - my $join; - - # join to the attachments table - my $attach_table = "attachments_$chart_id"; - $join = { - table => 'attachments', - as => $attach_table, - from => "$bugs_table.bug_id", - to => "bug_id", - extra => [ ($self->_user->is_insider ? '' : "$attach_table.isprivate = 0") ], - }; - push(@$joins, $join); - - # join to the flags table - my $flags_table = "flags_$chart_id"; - $join = { - table => 'flags', - as => $flags_table, - from => "$bugs_table.bug_id", - to => "bug_id", - extra => [ "($flags_table.attach_id = $attach_table.attach_id " . - " OR $flags_table.attach_id IS NULL)" ], - }; - push(@$joins, $join); - - # join to the flagtypes table - my $flagtypes_table = "flagtypes_$chart_id"; - $join = { - table => 'flagtypes', - as => $flagtypes_table, - from => "$flags_table.type_id", - to => "id", - }; - push(@$joins, $join); - - # join to the profiles table for the requestee - my $flag_profile_table = "flag_profiles_$chart_id"; - $join = { - table => 'profiles', - as => $flag_profile_table, - from => "$flags_table.requestee_id", - to => "userid", - }; - push(@$joins, $join); - - $args->{full_field} = $dbh->sql_string_concat("$flagtypes_table.name", - "$flags_table.status", - "COALESCE($flag_profile_table.login_name, '')"); - - $self->_do_operator_function($args); -} - sub _multiselect_nonchanged { my ($self, $args) = @_; my ($chart_id, $joins, $field, $operator) = diff --git a/Bugzilla/Search/Clause.pm b/Bugzilla/Search/Clause.pm index 5f5ea5b50..a068ce5ed 100644 --- a/Bugzilla/Search/Clause.pm +++ b/Bugzilla/Search/Clause.pm @@ -93,29 +93,25 @@ sub walk_conditions { sub as_string { my ($self) = @_; - if (!$self->{sql}) { - my @strings; - foreach my $child (@{ $self->children }) { - next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions; - next if $child->isa('Bugzilla::Search::Condition') - && !$child->translated; + my @strings; + foreach my $child (@{ $self->children }) { + next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions; + next if $child->isa('Bugzilla::Search::Condition') + && !$child->translated; - my $string = $child->as_string; - next unless $string; - if ($self->joiner eq 'AND') { - $string = "( $string )" if $string =~ /OR/; - } - else { - $string = "( $string )" if $string =~ /AND/; - } - push(@strings, $string); + my $string = $child->as_string; + if ($self->joiner eq 'AND') { + $string = "( $string )" if $string =~ /OR/; } - - my $sql = join(' ' . $self->joiner . ' ', @strings); - $sql = "NOT( $sql )" if $sql && $self->negate; - $self->{sql} = $sql; + else { + $string = "( $string )" if $string =~ /AND/; + } + push(@strings, $string); } - return $self->{sql}; + + my $sql = join(' ' . $self->joiner . ' ', @strings); + $sql = "NOT( $sql )" if $sql && $self->negate; + return $sql; } # Search.pm converts URL parameters to Clause objects. This helps do the diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm index a5144d0e0..7424f831f 100644 --- a/Bugzilla/Search/Quicksearch.pm +++ b/Bugzilla/Search/Quicksearch.pm @@ -376,8 +376,10 @@ sub _handle_field_names { my ($or_operand, $negate, $unknownFields, $ambiguous_fields) = @_; # Flag and requestee shortcut - if ($or_operand =~ /^(?:flag:)?([^\?]+\?[^\?]*)$/) { + if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) { addChart('flagtypes.name', 'substring', $1, $negate); + $chart++; $and = $or = 0; # Next chart for boolean AND + addChart('requestees.login_name', 'substring', $2, $negate); return 1; } -- cgit v1.2.3-24-g4f1b From 2aecaaf29af1866367a362bbd57c6b5258d6440e Mon Sep 17 00:00:00 2001 From: Frédéric Buclin Date: Thu, 18 Apr 2013 01:26:19 +0200 Subject: Bug 858911: Oracle fails with "ORA-04043: object T_GROUP_CONCAT does not exist" when installing Bugzilla for the first time r=dkl a=LpSolit --- Bugzilla/DB/Oracle.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Bugzilla/DB/Oracle.pm b/Bugzilla/DB/Oracle.pm index ebf59533f..622609f15 100644 --- a/Bugzilla/DB/Oracle.pm +++ b/Bugzilla/DB/Oracle.pm @@ -550,7 +550,9 @@ sub bz_setup_database { . " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;"); # Create types for group_concat - $self->do("DROP TYPE T_GROUP_CONCAT"); + my $type_exists = $self->selectrow_array("SELECT 1 FROM user_types + WHERE type_name = 'T_GROUP_CONCAT'"); + $self->do("DROP TYPE T_GROUP_CONCAT") if $type_exists; $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT " . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256)" . ", MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2" -- cgit v1.2.3-24-g4f1b