Sei sulla pagina 1di 14

Extending Toad for Oracle’s Code Xpert with the

SonarSource dashboard
Olivier Gaudin – SonarSource SA

Toad for Oracle’s Code Xpert utility (part of the Toad Professional Edition) is
one of the best PL/SQL code analyzers available on the market, embedding
the know-how of leading PL/SQL experts such as Steven Feuerstein.
However, although there is the option to store data in the Code Xpert
repository for point-in-time reporting, it is still primarily a static reporting
tool and therefore cannot report dynamically on code quality trends as part
of an overall quality strategy in a company, without manually querying the
repository tables.

SonarSource is a company whose main objective is democratization of


source code quality management. To reach this objective, the development
of Sonar, an open source platform to manage source code quality, started
two years ago.
Sonar enables the collection, analysis and reporting of quality metrics on
source code. Its functionality is articulated around 7 strengths:

1. a flexible and powerful data collection engine


2. a synthetic project dashboard, showing every axis of quality
analysis (coding rules, comments density, potential bugs’
detection, code duplication, unit tests, standards metrics and code
complexity)
3. a centralized configuration using quality profiles with associated
alerts
4. a TimeMachine to follow trends and compare versions
5. several tools to chase defects
6. a consolidated dashboard showing all projects at a glance
7. extensibility through a powerful API and a plugins forge

Originally built for Java, Sonar has been extended to include the PL/SQL
language through a commercial plugin. In this article, I am going to explain
how Sonar leverages Toad’s Code Xpert unlocking its full potential in order
to provide a full code quality management product.

Running code analysis with Toad’s Code Xpert covers several axes of code
quality management However, this is not sufficient if you are required to
have a global approach. What do I mean by that? If you want to manage
your projects portfolio globally, follow the evolution throughout time, get
alerts and then maybe delegate some remediation work, you are left with a
lot of manual work to consolidate data, compare projects or versions... That
is where Sonar comes into play with the following features:

1. Quality Profile management


A quality profile is the set of rules that you choose to apply when you run
code analysis on projects. In an enterprise environment, you would certainly
expect that several quality profiles are defined and get associated
depending on the nature of the project.
Every rule defined in a quality profile is managed in Sonar. Priority,
activation and configuration are handled centrally in the tool.

In Sonar, the set of rules is coupled to thresholds on metrics in order to


trigger alerts. Therefore it becomes possible to mission Sonar to proactively
notify you if a project reaches more than 30% of rules violation for example.

2. Central reporting
Sonar centralizes reporting at two levels.

Firstly at the project level where it enables its user to get a comprehensive
dashboard (that extends to any directory) enabling to view all metrics at a
glance to evaluate axes that need to be worked on. On top of the metrics
covered by Code Xpert, Sonar also reports on duplicated code and
comments.
Secondly, at the projects portfolio level to be able to be able to get the big
picture projects portfolio. The configurable dashboard enables to compare
projects and quickly understand where risk lies. This dashboard is cross
language (Java and PL/SQL in this case).

3. Metric evolution

At every analysis, measures are recorded into the Sonar database and add
to historical data. Sonar has a TimeMachine that enables to go back in time,
compare versions and monitor evolution.
4. Chasing tools

Sonar embarks a collection of chasing tools to track down the defects.


Amongst them: files cloud, hotspots, drill downs
The treemap The files cloud

The hotspots The drill downs

5. The resource viewer

The resource viewer enables to display the source code “tainted” with a
specific type of defect. All defects of the selected type are highlighted within
the code.

Moreover, the plugin embarks a PL/SQL code extractor that enables the
analysis of any Oracle Forms application.

The Sonar PL/SQL plugin is now at version 1.5 and currently covers 4 quality
axes out of 6 possible. Here is the list of planned short-term evolutions:
• Integration of SQLScan
• Integration of File complexity (McCabe)
• Integration of Unit tests
Here are a few links to help you find out more about Sonar and the PL/SQL
plugin:
• Sonar web site: http://sonar.codehaus.org
• Sonar in action: http://nemo.sonar.codehaus.org
• Sample PL/SQL project :
http://nemo.sonarsource.org/project/index/nl.oracledeveloper:utpl
sql
• To download a limited version of the plugin :
http://www.sonarsource.com/plugins

About the author

Olivier Gaudin is co-founder and director of SonarSource S.A.; an IT company


created in November 2008 and based in Plan-les-Ouates, Switzerland.

He has 12 years experience in IT, mainly in the banking industry and


experience heading Development and Application support departments.

He has a strong background in SQL and has been involved in CMMI and ITIL
implementations and have an acute awareness of quality and reliability of
processes.

PL/SQL Source Code Control inside the


database – After Compile trigger for automatic
archiving
Source code control for PL/SQL is often discussed in the organizations that I encounter. The database
itself will always hold the latest version of the PL/SQL objects we are using or developing. The sources
can of course also be held in files and those files can be managed in Source Code Control systems like
CVS, Subversion, ClearCase, Visual Source Safe etc. that do version control.

Many tools for PL/SQL development like TOAD and PL/SQL Developer or even JDeveloper, do not
readily integrate with such tools for PL/SQL code. Besides, most of these tools are used by developers
directly on the PL/SQL source as it is held in the database. Developers do not write the code to file all
the time and they certainly do not check in their code after each change they make. They compile any
changes, sending the latest version of their code to be held in the database – overwriting the previous
state of their PL/SQL object – and that is that. Perhaps after a number of compilations they may save to
a file and at the end of the day or some large task they may check in to the Source Code Control system.

The potential issues with this way of working – and believe me, I have run into them on many occasions
– is that developers often lose stages of their code that later on they want to retrieve.

For example: you start working in the morning on a source that you retrieve from the filesystem or
source code control environment. You make changes, compiling them as you go along. At some point,
you have a pretty good intermediate result. You
then continue with some intricate changes and you make some additional modifications and all of a
sudden, you have messed up your code. And you would really like to return to that intermediate result.
But: where is it? No longer in the database – overwritten by the next compilation. Not on the file system,
as you forgot to save. And certainly not in the Source Code Control repository, as you did not check in.

Then there is the everpresent risk of two developers working on the same package at about the same
time. Compilation by the second developer will immediately override and therefore discard any changes
made by the first.

In this article I would like to outline a very simple mechanism for using the database itself as archive for
all these intermediate versions. The essential pieces for this ‘working archive’ or operational repository:
a table, a package and a trigger. The machinery is put into motion by a Oracle 9i and higher DDL Event
trigger, fired on the CREATE (or compile) event.
The table PLSQL_ARCHIVE will hold earlier versions of the PL/SQL objects in the schema you are
working on. For each previous version, it will
The table is set up to hold a number of values for each Version of PL/SQL Objects: the PL/SQL source,
the timestamp for the compilation/creation, the status, size and errors and when provided by the
developer through annotations in the PL/SQL source also the Author, the Version, a Branch, the Priority
and possibly additional Comments. We define the table like this:

create table plsql_archive( object_name varchar2(128), object_type


varchar2(19), object_owner varchar2(32), creation_time date, status
varchar2(7), source clob, errors clob, author varchar2(128),
version_label varchar2(32), seq number(10), branch varchar2(32),
source_size number(10), priority varchar2(32), comments varchar2(4000),
label varchar2(4000) )/

Whenever we compile – create or replace – a PL/SQL object, the CREATE DDL event is fired and we
can intercept that event with our own trigger. At that point we have various pieces of information that
we can use to insert a new record into the PLSQL_ARCHIVE table.

CREATE OR REPLACE TRIGGER plsql_archiver_trgAFTER CREATEON SCHEMABEGIN if


ora_dict_obj_type in ( 'PACKAGE', 'PACKAGE BODY', 'TRIGGER','FUNCTION',
'PROCEDURE') then plsql_archiver.archive_version ( p_object_name =>
ora_dict_obj_name , p_object_type => ora_dict_obj_type , p_object_owner
=> ora_dict_obj_owner , p_creation_time => systimestamp ); end if;END
plsql_archiver_trg;/

We need a Nested Table Type defined in the server:

create type string_table as table of varchar2(4000)/

The package plsql_archiver:

CREATE OR REPLACE package plsql_archiverasprocedure archive_version( p_object_name


in varchar2, p_object_type in varchar2, p_object_owner in varchar2,
p_creation_time in date);--procedure revert_to_previous_version -- undo last
change; can be called repeatedly( p_object_name in varchar2, p_object_type in
varchar2, p_object_owner in varchar2, p_purge_latest in varchar2 default 'Y' );--
procedure revert_to_version( p_object_name in varchar2, p_object_type in
varchar2, p_object_owner in varchar2, p_version_label in varchar2, p_branch
in varchar2, p_seq in number, p_purge_later in varchar2 default
'N' );-- wildcards allowedprocedure purge( p_object_name in varchar2,
p_object_type in varchar2, p_object_owner in varchar2 , p_status in
varchar2 default null, p_priority in varchar2 default null, p_from_datetime in
date default null, p_to_datetime in date default null, p_branch in
varchar2 default null, p_seq_from in number default null, p_seq_to in
number default null);--/* Not yet implemented:-- wildcards allowed-- this
procedure will link all specified object versions to the label specified through
p_labelprocedure create_stripe( p_label in varchar2, p_object_name in
varchar2 , p_object_type in varchar2, p_object_owner in varchar2 ,
p_from_datetime in date default null, p_to_datetime in date default null,
p_branch in varchar2 default null, p_seq in number default
null);*/function version_graph( p_object_name in varchar2, p_object_type in
varchar2, p_object_owner in varchar2 , p_show_version_label in varchar2 default
'Y' -- show the version label , p_show_seq in varchar2 default 'N' --
show the seq value for each version (appended to the version label if that is
requested too), p_show_datetime in varchar2 default 'N' -- display the
timestamp of the creation of each version, p_show_author in varchar2
default 'N' -- display the author of each version, p_show_labels in
varchar2 default 'N' -- display the labels or stripes a version is associated with,
p_show_status in varchar2 default 'N' -- display the status (VALID or
INVALID) of the version, p_show_comments in varchar2 default 'N' -- display
the Comments for each version) return string_table;end plsql_archiver;/

We will bother with the actual implementation of this package later on. Let’s first see how we can make
use of what we have created thusfar:

• Get an overview of all compilations in our schema, including timestamp and resulting status
• Get the errors for a particular "version" of a PL/SQL object
• Get the source at a particular moment in time, for example to compare with the current version
• Revert to a specifc stage in the development of a PL/SQL object
• Create a Stripe or Label, associating specific versions of all or at least many PL/SQL program
units with what could be a release, a patch or whatever the stripe is to signify
• Gather some analytical data on the PL/SQL development process: frequency of compilations,
evolution of number of errors and size of the source
• When the developer uses annotations to provide comments, priority, version label and branch we
can even retrieve version graphs

Some of the information stored in the PLSQL_ARCHIVE table is inherently available at AFTER
CREATE trigger time, such as the source, the errors, the object name, size and type and the compilation
time. Others will have to be provided as annotations or meta-data in the source code, specified by the
developer him or herself.

In the implementation of the PLSQL_ARCHIVER presented below, we make use of the following
annotations, largely following the style used in JavaDoc and also adopted in PLDoc – an open source
project for generation of technical documentation for PL/SQL code:

• @author – for the author or developer of the (current version of the) PL/SQL unit
• @version – the version label for the current instance of the PL/SQL object
• @label – the label or stripe that this current instance of the PL/SQL object is associated with;
examples of Labels are Releases, Patches, Versions of (reusable) Components etc.
• @branch – an indication of the particular branch or subproject the PL/SQL unit is currently
being reworked for; when not explicitly set, the MAIN branch is assumed. Examples of other
branches are ADD_SECURITY (a subproject), 1.0PTCH2 (Patch 2 on Release 1.0) and
10gR2_PORT (the collection instances that make use of new Oracle 10gR2 PL/SQL features.
• @priority – an indication of the extent of changes in this version compared to the previous
version; typicaly values are Pico, Minor, Normal, Major
• @comments – any additional description of this version of the object, for example the changes
with respect to the previous version

The developer would include these keywords or annotations somewhere in a comment section in the
PL/SQL object. For example:

create or replace procedure test_archiveris l_t number; /**** * Annotations for


the PLSQL_ARCHIVER framework * * @author Lucas Jellema * @version 1.2 *
@comments Just a simple trial procedure to build some version history for *
@priority Normal *****/ begin for i in 1..100 loop null; i=i+1; end
loop;end;/

Some simple examples:


To list the recent history of PL/SQL compilation:

select object_name, object_type, object_owner, source_size,


creation_time , errorsfrom plsql_archiveorderby creation_time desc/
To find the most recent compilation errors:

select *from ( select errors from plsql_archive order


by creation_time desc )where rownum =1 /

To undo the last "check in" or last compilation of an object:

begin plsql_archiver.revert_to_previous_version -- undo last change; can be


called repeatedly ( p_object_name => 'TEST_ARCHIVER' , p_object_type =>
'PROCEDURE' , p_object_owner => USER , p_purge => 'Y' -- delete the most
recently compiled code ) ;end;/

To view the entire version tree for our TEST_ARCHIVER object:

set long 4000set linesize 300set pagesize 1000select *from table


( plsql_archiver.version_graph ( 'TEST_ARCHIVER' ,
'PROCEDURE' , USER , 'Y' -- version_label
, 'Y' -- seq , 'Y' -- creation datetime , 'Y' -- author
, 'Y' -- labels , 'Y' -- status , 'Y' -- comments
) ) version_history/

It is probably wise to start with somewhat less information. Let’s say just the Version Label, the
Sequence, Status and the Creation Date Time. The result for our TEST_ARCHIVER object looks
something like:

select *from table ( plsql_archiver.version_graph ( 'TEST_ARCHIVER'


, 'PROCEDURE' , USER , 'Y' -- version_label
, 'Y' -- seq , 'Y' -- creation datetime , 'N' -- author
, 'N' -- labels , 'Y' -- status , 'N' -- comments
) )
version_history/COLUMN_VALUE-------------------------------------------------------
---------------------------------------------Version Graph for PROCEDURE
TEST_ARCHIVER (schema AGS)MAIN Patch 3 Release
2.0 ADD_SECUTIRY New_BRANCH1.1 ()09-
OCT 21:28:33 |1.1 ()09-OCT 21:28:43 |1.2 (1)09-OCT 21:39:03 |1.2 (2)09-OCT
21:40:21 |1.2 (3)09-OCT 21:40:22 |1.2 (4)09-OCT 21:40:23 |1.2 (5)09-OCT
22:11:57 1.2PTCH203_1.1 (1) |
09-OCT 22:11:59 |
1.2PTCH203_1.1 (2) 09-OCT 22:12:11
| 1.2PTCH203_1.2 (1)
09-OCT 22:12:20 |
1.2PTCH203_1.3 (1) 09-OCT 22:12:21
| 1.2PTCH203_1.3 (2)
09-OCT 22:14:14 1.0SECU_1.0 (1)
| 09-OCT 22:14:24
|
1.0SECU_1.1 (1)
09-OCT 22:14:34
|
1.0SECU_1.2 (1)
09-OCT 22:14:36
|
1.0SECU_1.2 (2)1.3 (1)
10-OCT 10:34:1211-OCT 06:14:12
| 1.3NEW_BRANCH1.0 (1) |
11-OCT 06:14:16
|
1.3NEW_BRANCH1.0 (2)1.4 (1)
11-OCT 06:14:5611-OCT 06:33:27
| |1.5 (1)11-OCT 06:33:33 |1.6 (1) *12-OCT 16:45:27 |

Note: the asterisk (*) indicates that the object version is INVALID or its PL/SQL code cannot be
compiled!

Next Steps
Things I can envision for next steps with this revolutionary technology framework:

• Include a Compare facility that can report the differences between two versions. If I feel really
incredibly brave, I will do Merge as well. Actually, I think there must be Java based Compare
and perhaps Merge components for Text; we could probably upload them as Java Stored
Procedures and wrap them within PL/SQL wrappers
• Provide a GUI for this database backend or repository
• Provide a migration from CVS to my source code control system (or perhaps the other way
round)
• Ensure that a call to REVERT_TO_VERSION does not itself create a new version upon
compilation (as it currently probably does).
• Provide automatic version labeling: if I check in the next version after 1.4 the tool will call it 1.5
for me. It could even look at the priority level – major, minor, pico, normal – and decide to go
from 1.4 to 1.4.1, 1.5, 2.0 or 1.4.0.1.
• Add support for other database objects such as Tables and Views

Resources

Download the sources for this article: plsql_Archiver.zip

Implementation of the PLSQL_ARCHIVER


The Package Body for PLSQL_ARCHIVER is on the second page of this article. You can also
download it, with all SQL DDL scripts.

The body of this package:

CREATE OR REPLACE package body plsql_archiveras-- forward declarations


(implementation is at the end of the package body)function get_code( p_name in
varchar2) return clob;function get_errors( p_name in varchar2, p_type in varchar2)
return clob;--procedure archive_version( p_object_name in varchar2,
p_object_type in varchar2, p_object_owner in varchar2, p_creation_time in date)
is l_code clob; l_object_type varchar2(32):= p_object_type; l_archive_rec
plsql_archive%rowtype; l_rowid rowid; l_errors clob; procedure debug ( p_txt in
varchar2 ) is begin dbms_output.put_line(substr(p_txt,1,255)); end; -- this
function tries to locate the specified keyword in the current code block; if the
keyword is found, -- the string following the keyword until the first newline
character (chr(10)) is returned function get_annotation ( p_keyword in varchar2
-- values include: version, author, branch, comments, priority ) return varchar2
is l_pos number(5); -- position of the keyword, including the @ character
l_pos2 number(5); -- position of the first chr(10) following the keyword
l_return varchar2(4000); begin debug(p_keyword); l_pos:= instr( l_code,
'@'||p_keyword); debug('pos of keyword in code '||l_pos); if l_pos > 0
then l_pos2:= instr( l_code, chr(10), l_pos, 1); -- find the first instance of
chr(10), starting from position l_pos in the l_code block debug( 'Position of
chr(10) after keyword '||l_pos2); if l_pos2 > 0 then l_return:=
ltrim(substr( l_code, l_pos + length(p_keyword)+2, l_pos2 - (l_pos +
length(p_keyword)+2))); debug('return value '||l_return); end if;
end if; return l_return; end get_annotation;begin l_archive_rec.object_name:=
p_object_name; l_archive_rec.object_type:= p_object_type;
l_archive_rec.object_owner:= p_object_owner; l_archive_rec.creation_time:=
sysdate; if l_object_type = 'PACKAGE BODY' then l_object_type:= 'PACKAGE';
-- make sure that dbms_metadata does return the package body
DBMS_METADATA.SET_TRANSFORM_PARAM ( transform_handle =>
dbms_metadata.SESSION_TRANSFORM , name => 'BODY' , value
=> true , object_type => 'PACKAGE' ); -- make sure that
dbms_metadata does not return the package specification as well
DBMS_METADATA.SET_TRANSFORM_PARAM ( transform_handle =>
dbms_metadata.SESSION_TRANSFORM , name => 'SPECIFICATION' ,
value => false , object_type => 'PACKAGE' ); elsif
l_object_type = 'PACKAGE' then -- make sure that dbms_metadata does return the
package body DBMS_METADATA.SET_TRANSFORM_PARAM ( transform_handle =>
dbms_metadata.SESSION_TRANSFORM , name => 'BODY' , value
=> false , object_type => 'PACKAGE' ); -- make sure that
dbms_metadata does not return the package specification as well
DBMS_METADATA.SET_TRANSFORM_PARAM ( transform_handle =>
dbms_metadata.SESSION_TRANSFORM , name => 'SPECIFICATION' ,
value => true , object_type => 'PACKAGE' ); end if;
begin l_code:= dbms_metadata.get_ddl(l_object_type, p_object_name,
p_object_owner); exception when others then l_archive_rec.comments:=
sqlerrm; l_code:= get_code(p_object_name); end; l_archive_rec.source_size:=
length(l_code); l_errors:= get_errors( p_name => p_object_name, p_type =>
p_object_type); l_archive_rec.source:= get_code( p_name => p_object_name);
l_archive_rec.errors:= l_errors; begin select status into
l_archive_rec.status from all_objects where object_name = p_object_name
and object_type = p_object_type ; exception when others then if
l_errors is null or length(l_errors) <2 then l_archive_rec.status:=
'VALID'; else l_archive_rec.status:= 'INVALID'; end if; end;
l_archive_rec.version_label:= get_annotation('version'); l_archive_rec.comments:=
get_annotation('comments'); l_archive_rec.branch:= get_annotation('branch');
l_archive_rec.priority:= get_annotation('priority'); l_archive_rec.author:=
get_annotation('author'); -- find the max seq for objects on the same branch with
the same version label -- this assumes that either no version labels are used at
all or that every object has a version label -- and it is only changed once in a
while when a new meaningful stage has been reached for a particular object select
nvl(max(seq),0)+1 into l_archive_rec.seq from plsql_archive where
object_name = l_archive_rec.object_name and object_type =
l_archive_rec.object_type and object_owner = l_archive_rec.object_owner and
nvl(branch,'MAIN') = nvl(l_archive_rec.branch,'MAIN') and nvl(version_label,
'x.y') = nvl(l_archive_rec.version_label, 'x.y') ; insert into plsql_archive
values l_archive_rec returning rowid into l_rowid;end archive_version;

procedure revert_to_previous_version -- undo last change; can be called repeatedly(


p_object_name in varchar2, p_object_type in varchar2, p_object_owner in
varchar2, p_purge_latest in varchar2 default 'Y' ) is l_source clob; procedure
debug ( p_txt in varchar2 ) is begin
dbms_output.put_line(substr(p_txt,1,255)); end; begin select source into
l_source from ( select pae.source , row_number() over (partition
by object_name, object_type, object_owner
order by creation_time desc) rn from plsql_archive pae
where object_name = p_object_name and object_type = p_object_type
and object_owner = p_object_owner ) all_versions where rn = 2 ; if
p_purge_latest = 'Y' then -- delete last version from plsql_archive delete
from plsql_archive where rowid = ( select latest_version.rowid
from ( select pae.rowid , row_number() over
(partition by object_name, object_type, object_owner
order by creation_time desc) rn from plsql_archive
pae where object_name = p_object_name
and object_type = p_object_type and object_owner
= p_object_owner ) latest_version
where rn = 1 ) ; end if;
debug(l_source); -- create plsql object based on current source execute immediate
'create or replace '||cast( l_source as varchar2);

end revert_to_previous_version;

procedure revert_to_version( p_object_name in varchar2, p_object_type in


varchar2, p_object_owner in varchar2, p_version_label in varchar2, p_branch
in varchar2, p_seq in number, p_purge_later in varchar2 default 'N' )
is l_source clob; l_creation_time date; procedure debug ( p_txt in varchar2 )
is begin dbms_output.put_line(substr(p_txt,1,255)); end; begin select source
, creation_time into l_source , l_creation_time from plsql_archive
where object_name = p_object_name and object_type = p_object_type and
object_owner = p_object_owner and nvl(version_label, 'X') =
nvl(p_version_label,nvl(version_label, 'X')) and nvl(branch, nvl(p_branch,'X'))
= nvl(p_branch,nvl(branch,'X')) and nvl(seq, 0) = nvl(p_seq,nvl(seq, 0))
; if p_purge_later = 'Y' then -- delete last version from plsql_archive
delete from plsql_archive where object_name = p_object_name and
object_type = p_object_type and object_owner = p_object_owner and
nvl(branch, nvl(p_branch,'X')) = nvl(p_branch,nvl(branch,'X')) and
creation_time > l_creation_time ; end if; debug(l_source); --
create plsql object based on current source execute immediate 'create or replace
'||cast( l_source as varchar2); end revert_to_version;

procedure purge( p_object_name in varchar2, p_object_type in varchar2,


p_object_owner in varchar2 , p_status in varchar2 default null, p_priority
in varchar2 default null, p_from_datetime in date default null, p_to_datetime
in date default null, p_branch in varchar2 default null, p_seq_from
in number default null, p_seq_to in number default null) isbegin
delete from plsql_archive where object_name like p_object_name and
object_type like p_object_type and object_owner like p_object_owner and
nvl(branch, nvl(p_branch,'X')) like nvl(p_branch,nvl(branch,'X')) and
nvl(seq, 0) between nvl(p_seq_from,nvl(seq, 0)) and nvl(p_seq_to,nvl(seq, 0))
and creation_time between nvl(p_from_datetime,creation_time) and
nvl(p_to_datetime,creation_time) and nvl(priority, nvl(p_priority,'X')) like
nvl(p_priority,nvl(priority,'X')) and nvl(status, nvl(p_status,'X')) like
nvl(p_status,nvl(status,'X')) ;end purge;

function version_graph( p_object_name in varchar2, p_object_type in varchar2,


p_object_owner in varchar2 , p_show_version_label in varchar2 default 'Y' -- show
the version label , p_show_seq in varchar2 default 'N' -- show the seq
value for each version (appended to the version label if that is requested too),
p_show_datetime in varchar2 default 'N' -- display the timestamp of the
creation of each version, p_show_author in varchar2 default 'N' -- display
the author of each version, p_show_labels in varchar2 default 'N' --
display the labels or stripes a version is associated with, p_show_status in
varchar2 default 'N' -- display the status (VALID or INVALID) of the version,
p_show_comments in varchar2 default 'N' -- display the Comments for each
version) return string_tableis l_graph string_table:= string_table('Version Graph
for '||p_object_type||' '||p_object_name||' (schema '||p_object_owner||')');
l_line varchar2(32000);/*start with the MAIN branch, then the branch who entered
the version history most recentlyMAIN SECU PATCH21.0
1.0PTHC2_1.01.1 1.1SECU1.0 1.1SECU1.1
1.0PTHC2_1.11.21.2(2)1.2(3) */ type branch_columns_type is
table of string_table index by varchar2(32); type version_history_type is table of
plsql_archive%rowtype; type branch_rec is record (branch varchar2(32), width
number(4)); type branches_tbl_type is table of branch_rec index by binary_integer;
l_branches_tbl branches_tbl_type; l_branch_columns branch_columns_type; l_branch
varchar2(32); l_vh version_history_type; idx number(5); -- index into l_vh
collection ctr number(6):=1; l_next_branch varchar2(32); l_vh_done boolean:=
false; l_more_on_branch boolean:= false;

function get_vh_tbl ( p_version in plsql_archive%rowtype , p_column_width in


number default 40 ) return string_table is l_return string_table:=
string_table(); l_comments_length number(5):= length(p_version.comments);

function ifThen ( p_test in boolean , p_value in varchar2 ) return


varchar2 is begin if p_test then return p_value; else return ''; end
if; end ifThen; begin if p_show_version_label='Y' or p_show_seq='Y' or
p_show_status='Y' then l_return.extend; l_return(l_return.last):=
p_version.version_label||' ('||p_version.seq||')'||ifThen(p_show_status='Y' and
p_version.status ='INVALID',' *'); end if; if p_show_author='Y' then
l_return.extend; l_return(l_return.last):= p_version.author; end if; if
p_show_datetime='Y' then l_return.extend; l_return(l_return.last):=
to_char(p_version.creation_time,'DD-MON HH24:MI:SS'); end if; if p_show_labels='Y'
and length(nvl(p_version.label,''))> 0then l_return.extend;
l_return(l_return.last):= 'Labels: '||p_version.label; end if; if
p_show_comments='Y' and l_comments_length>0 then for i in
1..trunc(l_comments_length/40)+1 loop l_return.extend;
l_return(l_return.last):= substr(p_version.comments, 1 + (i-1)*p_column_width,
least(p_column_width, l_comments_length - (1 + (i-1)*p_column_width))); end
loop; end if; -- p_show_labels l_return.extend; l_return(l_return.last):= '
|'; return l_return; end get_vh_tbl; procedure add ( p_string in varchar2)
is begin l_graph.extend; l_graph( l_graph.last) := p_string; end add;begin
-- get branches for branch in (select distinct
nvl(branch, 'MAIN') branch , first_value(creation_time) over
(partition by branch order by creation_time) start_branch ,
max(length(version_label)) over (partition by branch) longest_version_label
, max(length(to_char(seq))) over (partition by branch) longest_seq
from plsql_archive order by start_branch
) loop l_branches_tbl(ctr).branch:= branch.branch ;
l_branches_tbl(ctr).width:= greatest( 40,
branch.longest_version_label+branch.longest_seq+6, length(branch.branch)+3) ;
l_line:= l_line||rpad(l_branches_tbl(ctr).branch, l_branches_tbl(ctr).width, ' ');
ctr:= ctr+1; end loop; -- branch add(l_line);
select * bulk collect into l_vh from plsql_archive where object_name =
p_object_name and object_type = p_object_type and object_owner =
p_object_owner order by creation_time ;

idx:= l_vh.first; l_branch_columns(nvl(l_vh(idx).branch,'MAIN')):=


get_vh_tbl(l_vh(idx)); loop l_line:=''; -- loop over branches and for each
branch column, if there is a line to write: write it! l_more_on_branch:=
false; for i in 1..l_branches_tbl.count loop-- add('outside'||
l_branches_tbl(i).branch); if
l_branch_columns.exists(l_branches_tbl(i).branch) and
l_branch_columns(l_branches_tbl(i).branch).count > 0 then -- add('inside'||
l_branches_tbl(i).branch||' count '||
l_branch_columns(l_branches_tbl(i).branch).count); l_line:=l_line||
rpad(l_branch_columns(l_branches_tbl(i).branch)
(l_branch_columns(l_branches_tbl(i).branch).first),l_branches_tbl(i).width,' ');

l_branch_columns(l_branches_tbl(i).branch).delete(l_branch_columns(l_branches_tbl(i
).branch).first); else l_line:=l_line|| rpad('
',l_branches_tbl(i).width,' ');

end if; if l_branch_columns.exists(l_branches_tbl(i).branch) and


l_branch_columns(l_branches_tbl(i).branch).count > 0 then l_more_on_branch:=
true; end if; --add('end of'); end loop; add(l_line); -- if
there are more versions left to process if l_vh.exists(l_vh.next(idx)) then
l_next_branch:= nvl(l_vh(l_vh.next(idx)).branch,'MAIN'); -- if there is room
for another branch if not l_branch_columns.exists(l_next_branch) or
l_branch_columns(l_next_branch).count = 0 or
l_branch_columns( l_next_branch) is null then idx:= l_vh.next(idx);
l_branch_columns(l_next_branch) := get_vh_tbl(l_vh(idx), 40); -- instead of 40 here
we should indicate the width for column associated with branch l_next_branch
l_more_on_branch:= true; end if; end if; ctr:=ctr+1; -- failsafe, to
not run infinitely in this loop l_vh_done := l_vh.next(idx) is null; exit
when l_vh_done and not l_more_on_branch; exit when ctr>20000; end loop; -- now
we have to go through a couple of rounds to have all branch_columns completely wash
out their stacks return l_graph;exceptionwhen othersthen add(sqlerrm||'ctr = '||
ctr);return l_graph;

end version_graph;--function get_code( p_name in varchar2) return clobis l_code


clob:='';begin for src in (SELECT text FROM user_source WHERE name=p_name ORDER
BY line) loop l_code:= l_code||src.text; end loop; return l_code;end
get_code;--function get_errors( p_name in varchar2, p_type in varchar2) return
clobis -- based on the example in chapter 15 of Advanced Oracle PL/SQL -
Programming with packages last_line INTEGER := 0; l_errors clob:=''; CURSOR
err_cur IS SELECT line, text FROM user_errors WHERE name =
UPPER (p_name) AND type = UPPER (p_type) ORDER BY line; /* Local
Modules */ procedure add ( p_text in varchar2 ) is begin l_errors:=
l_errors||p_text; end add; PROCEDURE err_put_line (prefix_in IN VARCHAR2,
text_in IN VARCHAR2) IS BEGIN add (RTRIM (RPAD (prefix_in, ||
text_in, CHR(10))); END; -- PROCEDURE display_line (line_in IN INTEGER) IS
CURSOR src_cur IS SELECT S.line, S.text FROM user_source S
WHERE S.name = UPPER (p_name) AND S.type = UPPER (p_type) AND
S.line = line_in; src_rec src_cur%ROWTYPE; BEGIN OPEN src_cur; FETCH
src_cur INTO src_rec; IF src_cur%FOUND THEN err_put_line (TO_CHAR
(line_in), src_rec.text); END IF; CLOSE src_cur; END; -- PROCEDURE
display_err (line_in IN INTEGER) IS CURSOR err_cur IS SELECT
line, position, text FROM user_errors WHERE name = UPPER (p_name)
AND type = UPPER (p_type) AND line = line_in; err_rec err_cur
%ROWTYPE; BEGIN OPEN err_cur; FETCH err_cur INTO err_rec; IF err_cur
%FOUND THEN add ('ERR' || LPAD ('*', err_rec.position+5));
err_put_line ('ERR', err_rec.text); END IF; CLOSE err_cur; END; --BEGIN
/* Main body of procedure. Loop through all error lines. */ FOR err_rec IN
err_cur LOOP /* Show the surrounding code. */ FOR line_ind IN
err_rec.line-2 .. err_rec.line+2 LOOP IF last_line < line_ind
THEN display_line (line_ind); display_err (line_ind);
END IF; last_line := GREATEST (last_line, line_ind); END LOOP; END
LOOP; dbms_output.put_line(substr(l_errors,1,255)); return l_errors;END
get_errors;--end plsql_archiver;/

Potrebbero piacerti anche