DDL::Oracle - a DDL generator for Oracle databases


DDL-Oracle documentation Contained in the DDL-Oracle distribution.

Index


Code Index:

NAME

Top

DDL::Oracle - a DDL generator for Oracle databases

VERSION

Top

VERSION = 1.11

SYNOPSIS

Top

 use DBI;
 use DDL::Oracle;

 my $dbh = DBI->connect(
                         "dbi:Oracle:dbname",
                         "username",
                         "password",
                         {
                          PrintError => 0,
                          RaiseError => 1
                         }
                       );

 # Use default resize and schema options.
 # query default DBA_xxx tables (could use USER_xxx for non-DBA types)
 DDL::Oracle->configure( 
                         dbh    => $dbh,
                       );

 # Create a list of one or more objects
 my $sth = $dbh->prepare(
        "SELECT
                owner
              , table_name
         FROM
                dba_tables
         WHERE
                tablespace_name = 'MY_TBLSP'    -- your mileage may vary
        "
     );

 $sth->execute;
 my $list = $sth->fetchall_arrayref;

 my $obj = DDL::Oracle->new(
                             type  => 'table',
                             list  => $list,                          );
                           );

 my $ddl = $obj->create;      # or $obj->resize;  or $obj->drop;  etc.

 print $ddl;    # Use STDOUT so user can redirect to desired file.

 # Here's another example, this time for type 'components'.  This type
 # differs from the norm, because it has no owner and no name.

 my $obj = DDL::Oracle->new(
                             type => 'components',
                             list => [[ 'no owner','no name' ]]
                           );

 my $ddl = $obj->create;

 print $ddl;




DESCRIPTION

Top

Overview

Designed for Oracle DBA's and users. It reverse engineers database objects (tables, indexes, users, profiles, tablespaces, roles, constraints, etc.). It generates DDL to *resize* tables and indexes to the provided standard or to a user defined standard.

We originally wrote a script to defrag tablespaces, but as DBA's we regularly find a need for the DDL of a single object or a list of objects (such as all of the indexes for a certain table). So we took all of the DDL statement creation logic out of defrag.pl, and put it into the general purpose DDL::Oracle module, then expanded that to include tablespaces, users, roles, and all other dictionary objects.

Oracle tablespaces tend to become fragmented (now THAT's an understatement). Even when object sizing standards are adopted, it is difficult to get 100% compliance from users. And even if you get a high degree of compliance, objects turn out to be a different size than originally thought/planned -- small tables grow to become large (i.e., hundreds of extents), what was thought would be a large table ends up having only a few rows, etc. So the main driver for DDL::Oracle was the object management needs of Oracle DBA's. The "resize" method generates DDL for a list of tables or indexes. For partitioned objects, the "appropriate" size of EACH partition is calculated and supplied in the generated DDL.

Initialization and Constructor

configure

The configure method is used to supply the DBI connection and to set several session level attributes. These are:

      dbh      A reference to a valid DBI connection (obtained via
               DBI->connect).  This is the only mandatory attribute.

               NOTE: The user connecting should have SELECT privileges
                     on the following views (in addition to the DBA or
                     USER views), but see attributes 'heading' for
                     exceptions:

                         V$DATABASE [i.e., sys.V_$DATABASE table]

                     And, in order to generate CREATE SNAPSHOT LOG
                     statements, you will also need to create a PUBLIC
                     SYNONYM for DBA_SNAPSHOT_LOG_FILTER_COLS.  In
                     order for non-DBA users to do the same, you will
                     need to grant SELECT on this view to them (e.g.,
                     to PUBLIC).  Why Oracle Corp. feels this view is
                     of no interest to non-replication users is a
                     mystery to the author.

                     And, in order to generate CREATE INDEX statements
                     for indexes which have DESCending column(s) and/or
                     include FUNCTION based column(s), you must have
                     select privileges on SYS.COL$, wherein the real
                     name of the column or function definition is held.

      schema   Defines whether and what to use as the schema for DDL
               on objects which use this syntax.  "1" means use the
               owner of the object as the schema; "0" or "" means
               omit the schema syntax; any other arbtrary string will
               be imbedded in the DDL as the schema.  The default is "1".

      resize   Defines whether and what to use in resizing segments.
               "1" means resize segments using the default algorithm;
               "0" or "" means keep the current INITIAL and NEXT
               values; any other string will be interpreted as a
               resize definition.  The default is "1".

               To establish a user defined algorithm, define this with
               a string consisting of n sets of LIMIT:INITIAL:NEXT.
               LIMIT is expressed in Database Blocks.  The highest LIMIT
               may contain the string 'UNLIMITED', and in any event will
               be forced to be so by DDL::Oracle.

      view     Defines which Dictionary views to query:  DBA or USER
               (e.g., DBA_TABLES or USER_TABLES).  The default is DBA.

      heading  Defines whether to include a Heading having Host, Instance,
               Date/Time, List of generated Objects, etc.  "1" means 
               include the heading; "0" or "" means to suppress the
               heading (and eliminate the query against V$DATABASE).
               The default is "1".

      prompt   Defines whether to include a PROMPT statement along
               with the DDL.  If the output is intended for use in
               SQL*Plus, this will cause SQL*Plus to display a comment
               about each statement before it executes, which can be
               helpful in a multi-statement file.  "1" means include
               the prompt; "0" or "" means to suppress the prompt.

      grants   Defines whether to include object grants following a
               CREATE statement for Tables, Views, Materialized Views
               [Snapshots], Sequences, Procedures, Functions, Packages,
               Types and Synonyms.   "1" means include the grant state-
               ments; "0" or "" means to suppress the grant statements.
               The default is "0".

      storage  Defines whether to include the STORAGE clause [plus
               PCTUSED, PCTFREE, INITRANS and MAXTRANS] in DDL for tables
               and indexes. "1" means include these clauses; "0" or ""
               means omit them. The default is "1".

      tblspace Defines whether to include the TABLESPACE clause in DDL
               for tables and indexes.  "1" means include this clause;
               "0" or "" means omit it. The default is "1".
new  

The new method is the object constructor. The two mandatory object definitions are supplied with this method, to wit:

      type    The type of object (e.g., TABLE, INDEX, SYNONYM, family,
              etc.).

              For 'table family', supply the name(s) of tables -- the
              DDL will include the table and its:
                  Comments (Table and Column)
                  Indexes
                  Constraints
                  Triggers

      list    An arrayref to an array of arrayrefs (as in the DBI's 
             "fetchall_arrayref" method) containing pairs of owner and
              name.

Object methods

create

The create method generates the DDL to create the list of Oracle objects. Virtually every type of Oracle object (table, index, user, synonym, trigger, etc.) can be CREATEd via this method -- see hash %create for a complete list. There is also a type called 'components' which is an aggregate type. It generates the DDL for all objects of types which do not have an owner (tablespace, rollback segment, profile, role, user) and for PUBLIC database links and synonyms. See the SYNOPSIS for an example of this.

drop

The drop method generates the DDL to drop the list of Oracle objects.

resize

The resize method generates the DDL to resize the list of Oracle objects. The 'type' defined in the 'new' method is limited to 'index' and 'table'. For tables, this generates an ALTER TABLE MOVE statement; for indexes, it generates an ALTER INDEX REBUILD statement. If the table or index is partitioned, then a statement for each partition is generated.

To generate DDL for a single partition of an index or table, define the 'name' as a colon delimited field (e.g., 'name:partition').

compile

The compile method generates the DDL to compile the list of Oracle objects. The 'type' defined in the 'new' method is limited to 'function', 'package', 'procedure', 'trigger' and 'view'.

show_space

The show_space method produces a report showing used/unused bytes and blocks above/below the high water mark in a segment. It includes the free blocks below the high water mark. For partitioned objects, it shows the information for each partition, with grand totals for the table/index. The object does NOT need to be analyzed for this report to be accurate -- it uses package sys.DBMS_SPACE to collect the data.

BUGS

Top

FILES

Top

 copy_user.pl
 copy_user.sh
 ddl.pl
 defrag.pl
 query.pl

AUTHOR

Top

 Richard V. Sutherland
 rvsutherland@yahoo.com

COPYRIGHT

Top


DDL-Oracle documentation Contained in the DDL-Oracle distribution.

# $Id: Oracle.pm,v 1.51 2002/05/22 15:26:01 rvsutherland Exp $ 
#
# Copyright (c) 2000, 2001 Richard Sutherland - United States of America
#
# See COPYRIGHT section in pod text below for usage and distribution rights.
#

require 5.004;

BEGIN
{
  $DDL::Oracle::VERSION = "1.11"; # Also update version in pod text below!
}

package DDL::Oracle;

use strict;

my $block_size;
my $dbh;
my $ddl;
my $host;
my $instance;
my $isasnapindx;
my $isasnaptabl;
my $oracle_major;
my $oracle_minor;
my $oracle_release;
my $sth;

my @size_arr;

my %attr;

my %compile = 
(
  'function'              => \&_compile,
  'package'               => \&_compile_package,
  'procedure'             => \&_compile,
  'trigger'               => \&_compile,
  'view'                  => \&_compile,
);

my %create = 
(
  'comments'              => \&_create_comments,
  'components'            => \&_create_components,
  'constraint'            => \&_create_constraint,
  'database link'         => \&_create_db_link,
  'exchange index'        => \&_create_exchange_index,
  'exchange table'        => \&_create_exchange_table,
  'function'              => \&_create_function,
  'index'                 => \&_create_index,
  'materialized view'     => \&_create_materialized_view,
  'materialized view log' => \&_create_materialized_view_log,
  'package'               => \&_create_package,
  'package body'          => \&_create_package_body,
  'procedure'             => \&_create_procedure,
  'profile'               => \&_create_profile,
  'role'                  => \&_create_role,
  'rollback segment'      => \&_create_rollback_segment,
  'schema'                => \&_create_schema,
  'sequence'              => \&_create_sequence,
  'snapshot'              => \&_create_snapshot,
  'snapshot log'          => \&_create_snapshot_log,
  'synonym'               => \&_create_synonym,
  'table'                 => \&_create_table,
  'table family'          => \&_create_table_family,
  'tablespace'            => \&_create_tablespace,
  'trigger'               => \&_create_trigger,
  'type'                  => \&_create_type,
  'user'                  => \&_create_user,
  'view'                  => \&_create_view,
);

my %drop = 
(
  'constraint'            => \&_drop_constraint,
  'database link'         => \&_drop_database_link,
  'dimension'             => \&_drop_schema_object,
  'directory'             => \&_drop_object,
  'function'              => \&_drop_schema_object,
  'index'                 => \&_drop_schema_object,
  'library'               => \&_drop_object,
  'materialized view'     => \&_drop_schema_object,
  'materialized view log' => \&_drop_materialized_view_log,
  'package'               => \&_drop_schema_object,
  'procedure'             => \&_drop_schema_object,
  'profile'               => \&_drop_profile,
  'role'                  => \&_drop_object,
  'rollback segment'      => \&_drop_object,
  'sequence'              => \&_drop_schema_object,
  'snapshot'              => \&_drop_schema_object,
  'snapshot log'          => \&_drop_snapshot_log,
  'synonym'               => \&_drop_synonym,
  'table'                 => \&_drop_table,
  'tablespace'            => \&_drop_tablespace,
  'trigger'               => \&_drop_schema_object,
  'type'                  => \&_drop_schema_object,
  'user'                  => \&_drop_user,
  'view'                  => \&_drop_schema_object,
);

my %show_space =
(
  'index'                 => \&_show_free_space,
  'table'                 => \&_show_free_space,
  'cluster'               => \&_show_free_space,
);

my %resize =
(
  'index'                 => \&_resize_index,
  'table'                 => \&_resize_table,
);

############################# Class Methods ############################

sub configure
{
  my ( $class, %args ) = @_;

  # Turn warnings off
  $^W = 0;

  $dbh               = $args{ 'dbh'  };
  $attr{ 'view' }    = ( "\U$args{ 'view'  }" eq 'USER' ) ? 'USER' : 'DBA';
  $attr{ 'schema'  } = ( exists $args{ 'schema'  } ) ? $args{ 'schema'  } : 1;
  $attr{ 'resize'  } = ( exists $args{ 'resize'  } ) ? $args{ 'resize'  } : 1;
  $attr{ 'prompt'  } = ( exists $args{ 'prompt'  } ) ? $args{ 'prompt'  } : 1;
  $attr{ 'grants'  } = ( exists $args{ 'grants'  } ) ? $args{ 'grants'  } : 0;
  $attr{ 'heading' } = ( exists $args{ 'heading' } ) ? $args{ 'heading' } : 1;
  $attr{ 'storage' } = ( exists $args{ 'storage' } ) ? $args{ 'storage' } : 1;
  $attr{ 'tblspace'} = ( exists $args{ 'tblspace'} ) ? $args{ 'tblspace'} : 1;

  _set_sizing();
  _get_oracle_release();
}

sub new
{
  my ( $class, %args ) = @_;

  my $self = {};

  $self->{ type }  = $args{ type } || die
                     "\nAttribute 'type' is required " .
                     "in call to method 'new'.\n\n";;
  $self->{ list }  = $args{ list } || die
                     "\nAttribute 'list' is required " .
                     "in call to method 'new'.\n\n";;

  return bless $self, $class;
}

########################### Instance Methods ###########################

sub compile
{
  my $self = shift;
  my $type = lc( $self->{ type } );

  die "\nObject type '$type' is invalid for 'compile' method.\n\n"
    unless $compile{ $type };

  my $list  = $self->{ list };
  my $class = ref( $self );
  _generate_heading( $class, 'COMPILE', $type, $list );

  foreach my $row ( @$list )
  {
    my ( $owner, $name ) = @$row;
    my $schema = _set_schema( $owner );

    $ddl .= $compile{ $type }->( 
                                 $schema, 
                                 $owner, 
                                 $name, 
                                 $attr{ view }, 
                                 $type,
                               ); 
  }

  _scratch_prompts()    unless $attr{ prompt };
  return $ddl;
}

sub create
{
  my $self = shift;
  my $type = lc( $self->{ type } );

  die "\nObject type '$type' is invalid for 'create' method.\n\n"
    unless $create{ $type };

  my $list  = $self->{ list };
  my $class = ref( $self );
  _generate_heading( $class, 'CREATE', $type, $list );

  foreach my $row ( @$list )
  {
    my ( $owner, $name ) = @$row;
    my $schema = _set_schema( $owner );

    $ddl .= $create{ $type }->( $schema, $owner, $name, $attr{ view } ); 
  }

  _scratch_prompts()    unless $attr{ prompt };
  return $ddl;
}

sub drop
{
  my $self = shift;
  my $type = lc( $self->{ type } );

  die "\nObject type '$type' is invalid for 'drop' method.\n\n"
    unless $drop{ $type };

  my $list  = $self->{ list };
  my $class = ref( $self );
  _generate_heading( $class, 'DROP', $type, $list );

  foreach my $row ( @{ $self->{ list } } )
  {
    my ( $owner, $name ) = @$row;
    my $schema = _set_schema( $owner );

    $ddl .= $drop{ $type }->( $schema, $name, $type, $owner,  $attr{ view } ); 
  }

  _scratch_prompts()    unless $attr{ prompt };
  return $ddl;
}

sub show_space
{
  my $self = shift;
  my $type = lc( $self->{ type } );

  die "\nObject type '$type' is invalid for 'show_space' method.\n\n"
    unless $show_space{ $type };

  my $list  = $self->{ list };
  my $class = ref( $self );
  _generate_heading( $class, 'FREE SPACE', $type, $list );

  foreach my $row ( @{ $self->{ list } } )
  {
    my ( $owner, $name ) = @$row;

    $ddl .= $show_space{ $type }->( $owner, $name, $type, $attr{ view } ); 
  }

  _scratch_prompts()    unless $attr{ prompt };
  return $ddl;
}

sub resize
{
  my $self = shift;
  my $type = lc( $self->{ type } );

  die "\nObject type '$type' is invalid for 'resize' method.\n\n"
   unless $resize{ $type };

  my $list  = $self->{ list };
  my $class = ref( $self );
  _generate_heading( $class, 'ALTER', $type, $list );

  foreach my $row ( @{ $self->{ list } } )
  {
    my ( $owner, $name ) = @$row;
    my $schema = _set_schema( $owner );

    $ddl .= $resize{ $type }->( $schema, $owner, $name, $attr{ view } ); 
  }

  _scratch_prompts()    unless $attr{ prompt };
  return $ddl;
}

############################ Private Methods ###########################

# sub _compile
#
# Returns DDL to compile the named object in the form of:
#
#     ALTER <type> [schema.]<name> COMPILE [PACKAGE|BODY]
# 
sub _compile
{
  my ( $schema, $owner, $name, $view, $type ) = @_;

  $type = uc( $type );
  my $type1 = ( $type eq 'PACKAGE BODY' ) ? 'PACKAGE' : $type;
  my $type2 = ( $type eq 'PACKAGE BODY' ) ? ' BODY'    :
              ( $type eq 'PACKAGE'      ) ? ' PACKAGE' : undef;

  my $stmt =
      "
              SELECT
                            'Standing tall with my boots on!'
              FROM
                            ${view}_objects
              WHERE
                                    object_name = UPPER( ? )
                            AND object_type = ?
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND owner       = UPPER('$owner')
                ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name, $type );
  my @row = $sth->fetchrow_array;
  die "\u\L$type \U$name \Ldoes not exist.\n\n" unless @row;

  return "PROMPT " .
         "ALTER $type1 \L$schema$name \UCOMPILE$type2  \n\n" .
         "ALTER $type1 \L$schema$name \UCOMPILE$type2 ;\n\n";
}

# sub _compile_package
#
# Returns DDL to compile the named object in the form of:
#
#     ALTER <type> [schema.]<name> COMPILE [PACKAGE|BODY]
# 
sub _compile_package
{
  my ( $schema, $owner, $name, $view, $type ) = @_;

  my $sql;
  my $stmt =
      "
              SELECT
                            'Standing around with only my socks on!'
              FROM
                            ${view}_objects
              WHERE
                                    object_name = UPPER( ? )
                            AND object_type = ?
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND owner       = UPPER('$owner')
                ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name, 'PACKAGE' );
  my @row = $sth->fetchrow_array;
  die "Package \U$name \Ldoes not exist.\n\n" unless @row;

  $sql = _compile( @_ );

  $sth->execute( $name, 'PACKAGE BODY' );
  @row  = $sth->fetchrow_array;
  $sql .= _compile( $schema, $owner, $name, $view, 'PACKAGE BODY' )    if @row;

  return $sql;
}

#sub _constraint_columns
#
# Returns a formatted string containing the constraint columns.
#
sub _constraint_columns
{
  my ( $owner, $name, $view, ) = @_;

  my $stmt =
      "
              SELECT
                            LOWER(column_name)
              FROM
                            ${view}_cons_columns
              WHERE
                                    owner            = UPPER( ? )
                            AND constraint_name  = UPPER( ? )
              ORDER
                    BY
                          position
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $owner, $name );
  my $aref = $sth->fetchall_arrayref;

  my @cols;
  foreach my $row ( @$aref )
  {
    push @cols, $row->[0];
  }

  return "(\n    " .
         join ( "\n  , ", @cols ) .
         "\n)\n";
}

# sub _create_comments
#
# Returns DDL to create the comments on the named table and its columns
# in the form of:
#
#     COMMENT ON TABLE [schema.]<name> IS '<text>'
#     COMMENT ON COLUMN [schema.]<name>.<column> IS '<text>'
#
sub _create_comments
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql;
  my $stmt =
      "
              SELECT
                            comments
              FROM
                            ${view}_tab_comments
              WHERE
                                    table_name    = UPPER( ? )
                            AND comments IS NOT null
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND owner         = UPPER('$owner')
                ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    @$row->[0] =~ s/'/''/g;

    $sql .= "PROMPT " .
            "COMMENT ON TABLE \L$schema$name \UIS \E'@$row->[0]'  \n\n" .
            "COMMENT ON TABLE \L$schema$name \UIS \E'@$row->[0]' ;\n\n";
  }

  $stmt =
      "
              SELECT
                            column_name
                        , comments
              FROM
                            ${view}_col_comments
              WHERE
                                    table_name    = UPPER( ? )
                            AND comments IS NOT null
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND owner         = UPPER('$owner')
                ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    @$row->[1] =~ s/'/''/g;

    $sql .= "PROMPT " .
            "COMMENT ON COLUMN \L$schema$name.@$row->[0] " . 
              "IS '@$row->[1]'  \n\n" .
            "COMMENT ON COLUMN \L$schema$name.@$row->[0] " . 
              "IS '@$row->[1]' ;\n\n";
  }

  return $sql;
}

# sub _create_components
#
# Returns DDL to create ALL objects of the following object types:
#
#     TABLESPACE
#     ROLLBACK SEGMENT
#     PUBLIC DATABASE LINK
#     PUBLIC SYNONYM
#     PROFILE
#     ROLE
#     USER
#
sub _create_components
{
  ( undef, undef, undef, my $view ) = @_;

  die "\nYou must use the DBA views in order to CREATE COMPONENTS\n\n"
      unless $view eq 'DBA';

  my $sql;
  my $stmt;
  my $aref;
  my $row;

  # Get tablespaces
  $stmt =
      "
              SELECT
                            tablespace_name
              FROM
                            dba_tablespaces
              ORDER
                    BY
                            tablespace_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach $row ( @$aref )
  {
    $sql .= _create_tablespace( undef, undef, @$row->[0], $view );
  }

  # Get rollback segments
  $stmt =
      "
              SELECT
                            segment_name
              FROM
                            dba_rollback_segs
              ORDER
                    BY
                            segment_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach $row ( @$aref )
  {
    $sql .= _create_rollback_segment( undef, undef, @$row->[0], $view );
  }

  # Get PUBLIC database links
  $stmt =
      "
              SELECT
                            db_link
              FROM
                            dba_db_links
              WHERE
                            owner = 'PUBLIC'
              ORDER
                    BY
                            db_link
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach $row ( @$aref )
  {
    $sql .= _create_db_link( 'PUBLIC ', 'PUBLIC', @$row->[0], $view );
  }

  # Get PUBLIC synonyms
  $stmt =
      "
              SELECT
                            owner
                        , synonym_name
              FROM
                            dba_synonyms
              WHERE
                            owner = 'PUBLIC'
              ORDER
                    BY
                            owner
                        , synonym_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach $row ( @$aref )
  {
    my ( $owner, $name ) = @$row;
    my $schema = _set_schema( $owner );

    $sql .= _create_synonym( $schema, $owner, $name, $view );
  }

  # Get profiles
  $stmt =
      "
              SELECT DISTINCT
                            profile
              FROM
                            dba_profiles
              ORDER
                    BY
                            profile
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach $row ( @$aref )
  {
    $sql .= _create_profile( undef, undef, @$row->[0], $view );
  }

  # Get roles
  $stmt =
      "
              SELECT
                            role
              FROM
                            dba_roles
              ORDER
                    BY
                            role
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach $row ( @$aref )
  {
    $sql .= _create_role( undef, undef, @$row->[0], $view );
  }

  # Get users
  $stmt =
      "
              SELECT
                            username
              FROM
                            dba_users
              WHERE
                            username NOT IN (
                                                                    'NEXT_USER'
                                                                , 'PUBLIC'
                                                                , 'SYS'
                                                                , 'SYSTEM'
                                                            )
              ORDER
                    BY
                            username
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach $row ( @$aref )
  {
    $sql .= _create_user( undef, undef, @$row->[0], $view );
  }

  return $sql;
}

#     
#
# sub _create_constraint
#
# Returns DDL to create the named constraint in the form of:
#
#     ALTER TABLE [schema.]<name> ADD CONSTRAINT <name> <TYPE>
#     [<column list>
#
sub _create_constraint
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql;
  my $stmt;

  if ( $oracle_major == 7 )
  {
     $stmt =
      "
              SELECT
                            table_name
                        , constraint_type
                        , search_condition
                        , r_owner
                        , r_constraint_name
                        , delete_rule
                        , DECODE(
                                            status
                                          ,'ENABLED','ENABLE'
                                          ,          'DISABLE'
                                        )                          as enagle
              FROM
                            ${view}_constraints cn
              WHERE
                                    owner           = UPPER( ? )
                            AND constraint_name = UPPER( ? )
            ";
  }
  else
  {
     $stmt =
      "
              SELECT
                            table_name
                        , constraint_type
                        , search_condition
                        , r_owner
                        , r_constraint_name
                        , delete_rule
                        , DECODE(
                                            status
                                          ,'ENABLED','ENABLE'
                                          ,          'DISABLE'
                                        )                          as enagle
                        , deferrable
                        , deferred
              FROM
                            ${view}_constraints cn
              WHERE
                                    owner           = UPPER( ? )
                            AND constraint_name = UPPER( ? )
            ";
  }

  $dbh->{ LongReadLen } = 8192;    # Allows SEARCH_CONDITION length of 8K
  $dbh->{ LongTruncOk } = 1;

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $owner, $name );
  my @row = $sth->fetchrow_array;

  my (
      $table,
      $cons_type,
      $condition,
      $r_owner,
      $r_cons_name,
      $delete_rule,
      $enable,
      $deferrable,
      $deferred,
     ) = @row;

  $sql  = "PROMPT " .
          "ALTER TABLE \L$schema$table \UADD CONSTRAINT \L$name ";
  $sql .= ( $cons_type eq 'P' ) ? "PRIMARY KEY\n\n" :
          ( $cons_type eq 'U' ) ? "UNIQUE\n\n"      :
          ( $cons_type eq 'R' ) ? "FOREIGN KEY\n\n" :
                                  "CHECK\n\n"; 

  $sql .= "ALTER TABLE \L$schema$table \UADD CONSTRAINT \L$name " ;
  $sql .= ( $cons_type eq 'P' ) ? "PRIMARY KEY\n" :
          ( $cons_type eq 'U' ) ? "UNIQUE\n"      :
          ( $cons_type eq 'R' ) ? "FOREIGN KEY\n" :
                                  "\nCHECK ($condition)\n";

  if ( $cons_type ne 'C' )
  {
    $sql .= _constraint_columns( $owner, $name, $view, );
  }

  if ( $cons_type eq 'R' )
  {
    $stmt =
        "
                  SELECT
                                table_name
                  FROM
                                ${view}_constraints
                  WHERE
                                        constraint_name  = UPPER( ? )
                                AND owner            = UPPER( ? )
                ";

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $r_cons_name, $r_owner );
    my ( $table_name ) = $sth->fetchrow_array;

    $sql .= "REFERENCES \L$r_owner\.$table_name\n" .
            _constraint_columns( $r_owner, $r_cons_name, $view );

    if ( $delete_rule eq 'CASCADE' )
    {
      $sql .= "ON DELETE CASCADE\n";
    }
  }

  if ( $oracle_major < 8 )
  {
    # Syntax for Oracle7 doesn't like the ENABLE clause
    $sql .= "$enable\n"    unless $enable eq 'ENABLE';
  }
  else
  {
    $sql .= "$deferrable\n" .
            "INITIALLY $deferred\n";

    if ( $enable eq 'ENABLE' )
    {
      $sql .= "ENABLE NOVALIDATE\n";
    }
    else
    {
      $sql .= "DISABLE\n";
    }
  }

  return $sql .
         ";\n\n";
}

# sub _create_db_link
#
# Returns DDL to create the named database link in the form of:
#
#     CREATE [PUBLIC] DATABASE LINK <name>
#     CONNECT TO <user> IDENTIFIED BY <password>
#     USING '<connect string>'
#
sub _create_db_link
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $msg = "\nYou must use the DBA views in order to " .
            "CREATE a PUBLIC DATABASE LINK\n\n";
  if ( "\U$owner" eq 'PUBLIC' and $view ne 'DBA' )
  {
    die $msg;
  }

  my $sql;
  my $stmt;

  if ( $view eq 'DBA' )
  {
    $stmt =
      "
              SELECT
                            l.userid
                        , l.password
                        , l.host
              FROM
                            sys.link\$  l
                        , sys.user\$  u
              WHERE
                                    u.name    = UPPER('$owner')
                            AND l.owner\# = u.user\#
                            AND l.name LIKE UPPER('${name}%')
            ";
  }
  else      # view is USER
  {
    $stmt =
      "
              SELECT
                            username
                        , password
                        , host
              FROM
                            user_db_links
              WHERE
                            db_link LIKE UPPER('${name}%')
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  my @row = $sth->fetchrow_array;
  die "Database Link \U$name \Ldoes not exist.\n\n" unless @row;

  my ( $user, $password, $host ) = @row;

  my $is_public = ( "\U$owner" eq 'PUBLIC' ) ? ' PUBLIC' : '';

  return "PROMPT " .
         "CREATE$is_public DATABASE LINK \L$name\n\n" .
         "CREATE$is_public DATABASE LINK \L$name\n" .
         "CONNECT TO \L$user \UIDENTIFIED BY \L$password\n" .
         "USING '$host'\n" .
         ";\n\n";
}

# sub _create_exchange_index
#
# Returns DDL to create a temporary table as a mirror of the named partition.
# See sub _create_table for the format.  Physical attributes come from the
# partition.
#
sub _create_exchange_index
{
  my ( $schema, $owner, $name, $view ) = @_;

  ( $name, my $partition ) = split /:/, $name;

  my $sql;
  my $stmt =
      "
              SELECT
                            SUBSTR(segment_type,7)       AS type
                        , blocks
              FROM
                            ${view}_segments
              WHERE
                                    segment_name   = UPPER( ? )
                            AND partition_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner          = UPPER('$owner')
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name, $partition );
  my @row = $sth->fetchrow_array;
  die "Partition \U$partition \Lof \EIndex \U$name \Ldoes not exist,\n"
    unless @row;

  my ( 
       $type,
       $blocks,
     ) = @row;

  $stmt =
      "
              SELECT
                            LTRIM(i.degree)
                        , LTRIM(i.instances)
                        , i.table_name
                        , DECODE(
                                            i.uniqueness
                                          ,'UNIQUE',' UNIQUE'
                                          ,null
                                        )                       AS uniqueness
                        , DECODE(
                                            i.index_type
                                          ,'BITMAP',' BITMAP'
                                          ,null
                                        )                       AS index_type
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , p.pct_free
                        , DECODE(
                                            p.ini_trans
                                          ,0,1
                                          ,null,1
                                          ,p.ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            p.max_trans
                                          ,0,255
                                          ,null,255
                                          ,p.max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        , p.initial_extent
                        , p.next_extent
                        , p.min_extent
                        , DECODE(
                                            p.max_extent
                                          ,2147483645,'unlimited'
                                          ,           p.max_extent
                                        )                       AS max_extent
                        , p.pct_increase
                        , NVL(p.freelists,1)
                        , NVL(p.freelist_groups,1)
                        , LOWER(p.buffer_pool)          AS buffer_pool
                        , DECODE(
                                            p.logging
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(p.tablespace_name)      AS tablespace_name
                        , $blocks                       AS blocks
              FROM
                            ${view}_indexes       i
                        , ${view}_ind_${type}s  p
              WHERE
                                    p.index_name   = UPPER( ? )
                            AND p.${type}_name = UPPER( ? )
                            AND i.index_name   = p.index_name
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND p.index_owner  = UPPER('$owner')
                            AND i.owner        = p.index_owner
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name, $partition );
  @row = $sth->fetchrow_array;

  my $degree     = shift @row;
  my $instances  = shift @row;
  my $table      = shift @row;
  my $unique     = shift @row;
  my $bitmap     = shift @row;

  $sql = "PROMPT " .
         "CREATE$unique$bitmap INDEX \L$schema$name \UON \L$schema$table\n\n" .
         "CREATE$unique$bitmap INDEX \L$schema$name \UON \L$schema$table\n" .
         _index_columns( '', $owner, $name, $view, ) .
         "PARALLEL\n" .
         "(\n" .
         "  DEGREE            $degree\n" .
         "  INSTANCES         $instances\n" .
         ")\n";

  unshift @row, ( '' );        # Indent (none)

  $sql .= _segment_attributes( \@row ) .
          ";\n\n";

  return $sql;
}

# sub _create_exchange_table
#
# Returns DDL to create a temporary table as a mirror of the named partition.
# See sub _create_table for the format.  Physical attributes come from the
# partition.
#
sub _create_exchange_table
{
  my ( $schema, $owner, $name, $view ) = @_;

  ( $name, my $partition ) = split /:/, $name;

  my $stmt =
      "
              SELECT
                            SUBSTR(segment_type,7)       AS type
                        , blocks
              FROM
                            ${view}_segments
              WHERE
                                    segment_name   = UPPER( '$name' )
                            AND partition_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner          = UPPER('$owner')
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $partition );
  my @row = $sth->fetchrow_array;
  die "Partition \U$partition \Lof \ETable \U$name \Ldoes not exist,\n"
    unless @row;

  my ( 
       $type,
       $blocks,
     ) = @row;

  $stmt =
      "
              SELECT
                            DECODE(
                                            t.monitoring
                                          ,'NO','NOMONITORING'
                                          ,     'MONITORING'
                                        )                              AS monitoring
                        , t.table_name
                        , LTRIM(t.degree)                      AS degree
                        , LTRIM(t.instances)                   AS instances
                        , 'HEAP'                               AS organization
                        , DECODE(
                                            t.cache
                                          ,'y','CACHE'
                                          ,    'NOCACHE'
                                        )                              AS cache
                        , p.pct_used
                        , p.pct_free
                        , p.ini_trans
                        , p.max_trans
                        , p.initial_extent
                        , p.next_extent
                        , p.min_extent
                        , DECODE(
                                            p.max_extent
                                          ,2147483645,'unlimited'
                                          ,p.max_extent
                                        )                              AS max_extents
                        , p.pct_increase
                        , p.freelists
                        , p.freelist_groups
                        , LOWER(p.buffer_pool)                 AS buffer_pool
                        , DECODE(
                                            p.logging
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                              AS logging
                        , LOWER(p.tablespace_name)             AS tablespace_name
                        , $blocks - NVL(p.empty_blocks,0)      AS blocks
              FROM
                            dba_tables        t
                        , dba_tab_${type}s  p
              WHERE
                                    p.table_name   = UPPER('$name')
                            AND p.${type}_name = UPPER('$partition')
                            AND t.table_name   = p.table_name
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND p.table_owner    = UPPER('$owner')
                            AND t.owner          = p.table_owner
            ";
  }

  return _create_table_text( $stmt, $schema, $owner, $name, $view ) .
         ";\n\n";
}

# sub _create_function
#
# Returns DDL to create the named procedure in the form of:
#
#     CREATE OR REPLACE FUNCTION [schema.]<name>
#     AS
#     <source>
# 
# by calling _display_source
#
sub _create_function
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql =  _display_source( $schema, $owner, $name, $view, 'FUNCTION' );

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql ;
}

# sub _create_index
#
# Returns DDL to create the named index and its partition(s) in the form of:
#
#     CREATE INDEX [schema1.]<name> ON [schema2.]<table>
#     (
#       <column list>
#     )
#     INDEXTYPE IS <value>  -- for domain indexes
#     PARAMETERS   <value>  -- for domain indexes
#     [NO]MONIOTORING
#     PARALLEL
#     (
#       DEGREE     <value>
#       INSTANCES  <value>
#     )
#     PCTFREE      <value>
#     INITRANS     <value>
#     MAXTRANS     <value>
#     STORAGE
#     (
#       <storage clause>
#     )
#     [NO]LOGING
#     TABLESPACE   <name>
#     [<partitioning clause>]
#
sub _create_index
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql;
  my $stmt;

  if ( $oracle_major == 7 )
  {
     $stmt =
      "
              SELECT
                            'N/A'                           AS partitioned
                        , table_name
                        , table_owner
                        , DECODE(
                                            uniqueness
                                          ,'UNIQUE',' UNIQUE'
                                          ,null
                                        )
                        , null                            AS bitmap
                        , null                            AS domain
              FROM
                            ${view}_indexes
              WHERE
                                    index_name = UPPER( ? )
            ";
  }
  else               # We're Oracle8 or newer
  {
     $stmt =
      "
              SELECT
                            partitioned
                        , table_name
                        , table_owner
                        , DECODE(
                                            uniqueness
                                          ,'UNIQUE',' UNIQUE'
                                          ,null
                                        )
                        , DECODE(
                                            index_type
                                          ,'BITMAP',' BITMAP'
                                          ,null
                                        )
                        , DECODE(
                                            index_type
                                          ,'DOMAIN','DOMAIN'
                                          ,null
                                        )
              FROM
                            ${view}_indexes
              WHERE
                                    index_name = UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND owner      = UPPER('$owner')
                ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  die "Index \U$name \Ldoes not exist.\n\n" unless @row;

  my ( 
       $partitioned,
       $table,
       $table_owner,
       $unique,
       $bitmap,
       $domain, 
     ) = @row;

  my (
       $dom_owner,
       $dom_name,
       $dom_param
     );

  if ( $domain eq 'DOMAIN')
  {
     $stmt =
      "
              SELECT
                            ityp_owner
                        , ityp_name
                        , parameters
              FROM
                            ${view}_indexes
              WHERE
                                    index_name = UPPER( ? )
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
        "
                            AND owner      = UPPER('$owner')
                ";
    }

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my @row = $sth->fetchrow_array;

    (
      $dom_owner,
      $dom_name,
      $dom_param
    ) = @row;
  }

  if ( $oracle_major == 7 )
  {
    $stmt =
      "
              SELECT
                            'N/A'                         AS degree
                        , 'N/A'                         AS instances
                        , 0                             AS compressed
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , i.pct_free
                        , DECODE(
                                            i.ini_trans
                                          ,0,1
                                          ,null,1
                                          ,i.ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            i.max_trans
                                          ,0,255
                                          ,null,255
                                          ,i.max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        , i.initial_extent
                        , i.next_extent
                        , i.min_extents
                        , DECODE(
                                            i.max_extents
                                          ,2147483645,'unlimited'
                                          ,           i.max_extents
                                        )                       AS max_extents
                        , i.pct_increase
                        , NVL(i.freelists,1)
                        , NVL(i.freelist_groups,1)
                        , 'N/A'                         AS buffer_pool
                        , 'N/A'                         AS logging
                        , LOWER(i.tablespace_name)      AS tablespace_name
                        , s.blocks
              FROM
                            ${view}_indexes   i
                        , ${view}_segments  s
              WHERE
                                    i.index_name   = UPPER( ? )
                            AND s.segment_name = i.index_name
            ";
  }
  elsif ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            LTRIM(i.degree)
                        , LTRIM(i.instances)
                        , 0                             AS compressed
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , i.pct_free
                        , DECODE(
                                            i.ini_trans
                                          ,0,1
                                          ,null,1
                                          ,i.ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            i.max_trans
                                          ,0,255
                                          ,null,255
                                          ,i.max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        , i.initial_extent
                        , i.next_extent
                        , i.min_extents
                        , DECODE(
                                            i.max_extents
                                          ,2147483645,'unlimited'
                                          ,           i.max_extents
                                        )                       AS max_extents
                        , i.pct_increase
                        , NVL(i.freelists,1)
                        , NVL(i.freelist_groups,1)
                        , LOWER(i.buffer_pool)          AS buffer_pool
                        , DECODE(
                                            i.logging
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(i.tablespace_name)      AS tablespace_name
                        , s.blocks
              FROM
                            ${view}_indexes   i
                        , ${view}_segments  s
              WHERE
                                    i.index_name   = UPPER( ? )
                            AND s.segment_name = i.index_name
            ";
  }
  else               # We're Oracle8i or newer
  {
    $stmt =
      "
              SELECT
                            LTRIM(i.degree)
                        , LTRIM(i.instances)
                        , DECODE(
                                            i.compression
                                          ,'ENABLED',i.prefix_length
                                          ,0
                                        )                             AS compressed
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , i.pct_free
                        , DECODE(
                                            i.ini_trans
                                          ,0,1
                                          ,null,1
                                          ,i.ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            i.max_trans
                                          ,0,255
                                          ,null,255
                                          ,i.max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        , i.initial_extent
                        , i.next_extent
                        , i.min_extents
                        , DECODE(
                                            i.max_extents
                                          ,2147483645,'unlimited'
                                          ,           i.max_extents
                                        )                       AS max_extents
                        , i.pct_increase
                        , NVL(i.freelists,1)
                        , NVL(i.freelist_groups,1)
                        , LOWER(i.buffer_pool)          AS buffer_pool
                        , DECODE(
                                            i.logging
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(i.tablespace_name)      AS tablespace_name
                        , s.blocks
              FROM
                            ${view}_indexes   i
                        , ${view}_segments  s
              WHERE
                                    i.index_name   = UPPER( ? )
                            AND s.segment_name = i.index_name
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND i.owner        = UPPER('$owner')
                            AND s.owner        = i.owner
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  @row = $sth->fetchrow_array;

  my $degree     = shift @row;
  my $instances  = shift @row;
  my $compressed = shift @row;

  my $schema2 = _set_schema( $table_owner );

  $sql = "PROMPT " .
         "CREATE$unique$bitmap INDEX \L$schema$name \UON \L$schema2$table\n\n" .
         "CREATE$unique$bitmap INDEX \L$schema$name \UON \L$schema2$table\n" .
         _index_columns( '', $owner, $name, $view, );

  if ( $domain eq 'DOMAIN' )
  {
    return $sql .
    qq!INDEXTYPE IS "$dom_owner"."$dom_name"\nPARAMETERS ('$dom_param') ;\n\n!;
  }

  if ( $oracle_major > 7 )
  {
    $sql .= "PARALLEL\n" .
            "(\n" .
            "  DEGREE            $degree\n" .
            "  INSTANCES         $instances\n" .
            ")\n";
  }

  if ( $partitioned eq 'YES' )
  {
    return _create_partitioned_index( $schema, $owner, $name, $view, $sql )
  }
  else         # Plain ol' non-partitioned index
  {
    unshift @row, ( '' );        # Indent (none)

    $sql .= _segment_attributes( \@row );

    if ( $compressed )
    {
      $sql .= "COMPRESS            $compressed\n";
    }

    return $sql .= ";\n\n";
  }
}

# sub _create_iot
#
# Returns DDL to create the index organized table and its partition(s).
# See _create_table for format.
#
sub _create_iot
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $stmt;

  if ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            'N/A'                         AS monitoring
                        , logging
                        , blocks - NVL(empty_blocks,0)
              FROM
                            ${view}_all_tables
              WHERE
                                    table_name = UPPER( ? )
            ";
  }
  else
  {
    $stmt =
      "
              SELECT
                            DECODE(
                                            monitoring
                                          ,'NO','NOMONITORING'
                                          ,     'MONITORING'
                                        )                       AS monitoring
                        , logging
                        , blocks - NVL(empty_blocks,0)
              FROM
                            ${view}_all_tables
              WHERE
                                    table_name = UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner      = UPPER('$owner')
            ";
  }

  my $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my (
       $monitoring,
       $logging,
       $blocks,
     ) = $sth->fetchrow_array;

  if ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            -- Table Properties
                            'N/A'                         AS monitoring
                        , 'N/A'                         AS table_name
                            -- Parallel Clause
                        , LTRIM(degree)
                        , LTRIM(instances)
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , pct_free
                        , DECODE(
                                            ini_trans
                                          ,0,1
                                          ,null,1
                                          ,ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            max_trans
                                          ,0,255
                                          ,null,255
                                          ,max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        , initial_extent
                        , next_extent
                        , min_extents
                        , DECODE(
                                            max_extents
                                          ,2147483645,'unlimited'
                                          ,           max_extents
                                        )                       AS max_extents
                        , pct_increase
                        , NVL(freelists,1)
                        , NVL(freelist_groups,1)
                        , LOWER(buffer_pool)          AS buffer_pool
                        , DECODE(
                                            '$logging'
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(tablespace_name)      AS tablespace_name
                        , DECODE(
                                            '$blocks'
                                            ,null,GREATEST(initial_extent,next_extent) 
                                                        / ($block_size * 1024)
                                            ,'0' ,GREATEST(initial_extent,next_extent)
                                                        / ($block_size * 1024)
                                            ,'$blocks'
                                        )                       AS blocks
              FROM
                            ${view}_indexes
              WHERE
                                    table_name  = UPPER('$name')
            ";
  }
  else
  {
    $stmt =
      "
              SELECT
                            -- Table Properties
                            DECODE(
                                            '$monitoring'
                                          ,'NO','NOMONITORING'
                                          ,     'MONITORING'
                                        )
                        , 'N/A'                         AS table_name
                            -- Parallel Clause
                        , LTRIM(degree)
                        , LTRIM(instances)
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , pct_free
                        , DECODE(
                                            ini_trans
                                          ,0,1
                                          ,null,1
                                          ,ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            max_trans
                                          ,0,255
                                          ,null,255
                                          ,max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        , initial_extent
                        , next_extent
                        , min_extents
                        , DECODE(
                                            max_extents
                                          ,2147483645,'unlimited'
                                          ,           max_extents
                                        )                       AS max_extents
                        , pct_increase
                        , NVL(freelists,1)
                        , NVL(freelist_groups,1)
                        , LOWER(buffer_pool)          AS buffer_pool
                        , DECODE(
                                            '$logging'
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(tablespace_name)      AS tablespace_name
                        , DECODE(
                                            '$blocks'
                                            ,null,GREATEST(initial_extent,next_extent) 
                                                        / ($block_size * 1024)
                                            ,'0' ,GREATEST(initial_extent,next_extent)
                                                        / ($block_size * 1024)
                                            ,'$blocks'
                                        )                       AS blocks
              FROM
                            ${view}_indexes
              WHERE
                                    table_name  = UPPER('$name')
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND table_owner = UPPER('$owner')
            ";
  }

  my $sql = _create_table_text( $stmt, $schema, $owner, $name, $view ) .
            "; \n\n" .
            _create_comments( $schema, $owner, $name, $view );

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql;
}

# sub _create_materialized_view
#
# Returns DDL to create the named materialized view
# by calling _create_mview (which is shared with
# _create_snapshot)
#
sub _create_materialized_view
{
  _create_mview( @_, 'MATERIALIZED VIEW' );
}

# sub _create_materialized_view_log
#
# Returns DDL to create the named materialized view log
# by calling _create_mview_log (which is shared with
# _create_snapshot_log)
#
sub _create_materialized_view_log
{
  _create_mview_log( @_, 'MATERIALIZED VIEW' );
}

# sub _create_mview
# 
# Returns DDL to create the named snapshot or materialized view
# in the form of:
#
#     CREATE {MATERIALIZED VIEW|SNAPSHOT} [schema.]<name>
#     <table properties>
#
sub _create_mview
{
  my ( $schema, $owner, $name, $view, $type ) = @_;

  my $sql;
  my $stmt;

  if ( $oracle_major == 7 )
  {
    $stmt =
      "
              SELECT
                            table_name
                        , null                                     AS build_mode
                        , null                                     AS refresh_method
                        ,DECODE(
                                          type
                                        ,null,null
                                        ,'REFRESH ' || type || CHR(10)
                                      )                                   AS refresh_mode
                        , TO_CHAR(start_with, 'DD-MON-YYYY HH24:MI:SS')
                                                                                                              AS start_with
                        , next
                        , null                                     AS using_pk
                        , null                                     AS master_rollback_seg
                        , DECODE(
                                            updatable
                                          ,'TRUE','FOR UPDATE'
                                          ,null
                                        )                                  AS updatable
                        , query
              FROM
                          ${view}_snapshots
              WHERE
                                    name  = UPPER( ? )
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
        "
                            AND owner = UPPER('$owner')
                "
    }
  }
  elsif ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            table_name
                        , null                                     AS build_mode
                        , null                                     AS refresh_method
                        ,DECODE(
                                          type
                                        ,null,null
                                        ,'REFRESH ' || type || CHR(10)
                                      )                                   AS refresh_mode
                        , TO_CHAR(start_with, 'DD-MON-YYYY HH24:MI:SS')
                                                                                                              AS start_with
                        , next
                        , DECODE(
                                            refresh_method
                                          ,'PRIMARY KEY','WITH  PRIMARY KEY'
                                          ,'ROWID'      ,'WITH  ROWID'
                                          ,null
                                        )                                  AS using_pk
                        , master_rollback_seg
                        , DECODE(
                                            updatable
                                          ,'YES','FOR UPDATE'
                                          ,null
                                        )                                  AS updatable
                        , query
              FROM
                          ${view}_snapshots
              WHERE
                                    name  = UPPER( ? )
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
        "
                            AND owner = UPPER('$owner')
                "
    }
  }
  else
  {
    $stmt =
      "
              SELECT
                            m.container_name
                        , DECODE(
                                            m.build_mode
                                          ,'YES','USING PREBUILT TABLE'
                                          ,DECODE(
                                                            m.last_refresh_date
                                                          ,null,'BUILD DEFERRED'
                                                          ,'BUILD IMMEDIATE'
                                                        )
                                        ) || CHR(10)                       AS build_mode
                        , DECODE(
                                            m.refresh_method
                                          ,'NEVER','NEVER REFRESH'
                                          ,'REFRESH ' || m.refresh_method
                                        )                                  AS refresh_method
                        , DECODE(
                                            m.refresh_mode
                                          ,'NEVER',CHR(10)
                                          ,'ON ' || m.refresh_mode || CHR(10)
                                        )                                  AS refresh_mode
                        , TO_CHAR(s.start_with, 'DD-MON-YYYY HH24:MI:SS')
                                                                                                              AS start_with
                        , s.next
                        , DECODE(
                                            s.refresh_method
                                          ,'PRIMARY KEY','WITH  PRIMARY KEY'
                                          ,'ROWID'      ,'WITH  ROWID'
                                          ,null
                                        )                                  AS using_pk
                        , s.master_rollback_seg
                        , DECODE(
                                            m.updatable
                                          ,'N',null
                                          ,DECODE(
                                                            m.rewrite_enabled
                                                          ,'Y','FOR UPDATE ENABLE QUERY REWRITE'
                                                          ,'N','FOR UPDATE DISABLE QUERY REWRITE'
                                                        )
                                        )                                  AS updatable
                        , s.query
              FROM
                            ${view}_mviews     m
                        , ${view}_snapshots  s
              WHERE
                                    m.mview_name  = UPPER( ? )
                            AND s.name        = m.mview_name
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
        "
                            AND m.owner       = UPPER('$owner')
                            AND s.owner       = m.owner
                "
    }
  }

  $dbh->{ LongReadLen } = 65536;    # Allows Query to be 64K
  $dbh->{ LongTruncOk } = 1;

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  my $lctype = ( $type eq 'SNAPSHOT' ) ? 'Snapshpt' : 'Materialized View';
  die "\n$lctype \U$name \Ldoes not exist.\n\n" unless @row;

  my ( 
       $table, 
       $build_mode, 
       $refresh_method, 
       $refresh_mode,
       $start_with,
       $next,
       $using_pk,
       $master_rb_seg,
       $updatable,
       $query,
     ) = @row;

  $stmt =
      "
              SELECT
                            index_name
              FROM
                            ${view}_indexes
              WHERE
                                    table_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND owner      = UPPER('$owner')
                "; }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $table );
  @row = $sth->fetchrow_array;

  my ( $index ) = @row;

  $sql  = "PROMPT " .
          "CREATE $type \L$schema$name\n\n" .
          "CREATE $type \L$schema$name  \n" .
          _create_mview_table( $owner, $owner, $table, $view ) .
          "$build_mode" .
          "USING INDEX\n" .
          _create_mview_index( $schema, $owner, $index, $view );

  $sql .= "$refresh_method "    if $refresh_method;

  $sql .= $refresh_mode;

  if ( $refresh_method ne 'NEVER REFRESH' )
  {
    $sql .= "START WITH TO_DATE('$start_with','DD-MON-YYYY HH24:MI:SS')\n"
      if $start_with;

    $sql .= "NEXT  $next\n"    if $next;

    $sql .= "$using_pk\n"      if $using_pk;

    $sql .= "USING MASTER ROLLBACK SEGMENT \L$master_rb_seg\n"
      if $master_rb_seg;
  }

  $sql .= "$updatable\n"     if $updatable;

  $sql .= "AS\n" . $query;

  $sql =~ s/\n+\Z//;

  $sql .= " ;\n\n";

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql;
}

# sub _create_mview_index
#
# Returns DDL for the USING INDEX definition part of:
#
#     CREATE MATERIALIZED VIEW
#     CREATE SNAPSHOT
#
# statements.  This is created by calling _create_index, and
# then stripping off the PROMPT and CREATE INDEX portions and the
# column list, leaving just the physical attributes and partitioning clauses
#
sub _create_mview_index
{
  # Snapshots don't use attributes PCTUSED and PCTFREE.
  # This will prevent sub _segment_attributes from including them.
  $isasnapindx = 1;

  my $done;
  my $started;
  my @lines_in = split /\n/, _create_index( @_ );
  my @lines_out;

  LINE:
    foreach my $line ( @lines_in )
    {
      # Ignore everything before the INITRANS clause.
      # This includes REMs, CREATE INDEX, columns, etc.
      $started++    if $line =~ /^INITRANS/;
      next LINE     if not $started;

      # Set $done when we hit a semicolon
      $done = $line =~ s/\;$//;

      # But keep everything in between except blank lines
      # and any [NO]LOGGINING clauses
      push @lines_out, $line    unless $line =~ /^$|LOGGING/;

      # Exit when we get to the semicolon and ignore the rest.
      # This eliminates the ';' and any COMMENTs.
      last LINE if $done;
    }

  my $sql = join "\n", @lines_out;
  
  $isasnapindx = 0;

  return $sql .  "\n";
}

# sub _create_mview_log
# 
# Returns DDL to create the named log (snapshot or materialized view)
# in the form of:
#
#     CREATE {MATERIALIZED VIEW|SNAPSHOT} LOG [schema.]<name>
#     <table properties>
#     WITH {PRIMARY KEY|ROWID|PRIMARY KEY, ROWID}
#     [<filter columns>]
#
sub _create_mview_log
{
  my ( $schema, $owner, $name, $view, $type ) = @_;

  my $sql;
  my $stmt =
      "
              SELECT
                            log_table
                        , rowids
                        , primary_key
                        , filter_columns
              FROM
                            ${view}_snapshot_logs
              WHERE
                                    master     = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND log_owner  = UPPER('$owner')
                "; }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  my $lctype = ( $type eq 'SNAPSHOT' ) ? 'Snapshpt' : 'Materialized View';
  die "\n$lctype Log on \U$name \Ldoes not exist.\n\n" unless @row;

  my ( $table, $rowids, $primary_key, $filter_columns, $refreshable ) = @row;

  $sql  = "PROMPT " .
          "CREATE $type LOG ON \L$schema$name\n\n" .
          "CREATE $type LOG ON \L$schema$name  \n" .
          _create_mview_table( $schema, $owner, $table, $view );

  if ( $rowids eq 'YES' and $primary_key eq 'YES' )
  {
    $sql .= "WITH PRIMARY KEY, ROWID "
  }
  elsif ( $rowids eq 'YES' )
  {
    $sql .= "WITH ROWID "
  }
  elsif ( $primary_key eq 'YES' )
  {
    $sql .= "WITH PRIMARY KEY "
  }

  $stmt =
      "
              SELECT
                            column_name
              FROM
                            dba_snapshot_log_filter_cols
              WHERE
                                    name  = UPPER( ? )
                            AND owner = UPPER( ? )
              MINUS
              SELECT
                            column_name
              FROM
                            ${view}_cons_columns  c
                        , ${view}_constraints   d
              WHERE
                                    d.table_name      = UPPER( ? )
                            AND d.constraint_type = 'P'
                            AND c.table_name      = d.table_name
                            AND c.constraint_name = d.constraint_name
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND d.owner           = UPPER('$owner')
                            AND c.owner           = d.owner
                "; }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name, $owner, $name );
  my $aref = $sth->fetchall_arrayref;

  if ( @$aref )
  {

    my $comma  = '   ';
    $sql .= "\n(\n";

    foreach my $row ( @$aref )
    {
      $sql .= "$comma \L$row->[0]\n";
      $comma = '  ,'
    }

    $sql .= ") ";
  }

  return $sql .
         ";\n\n";
}

# sub _create_mview_table
#
# Returns DDL for the table definition part of:
#
#     CREATE MATERIALIZED VIEW
#     CREATE MATERIALIZED VIEW LOG
#     CREATE SNAPSHOT
#     CREATE SNAPSHOT LOG
#
# statements.  This is created by calling _create_table, and
# then stripping off the PROMPT and CREATE TABLE portions and the
# column list, leaving just the physical attributes and partitioning clauses
#
sub _create_mview_table
{
  # Snapshots and their logs don't use attribute INITRANS.
  # Prior to 8i, they don't use FREELISTS and FREELIST GROUPS, either.
  # This will prevent sub _segment_attributes from including it.
  $isasnaptabl = 1;

  my $done;
  my $started;
  my @lines_in = split /\n/, _create_table( @_ );
  my @lines_out;

  LINE:
    foreach my $line( @lines_in )
    {
      # Ignore everything before the PARALLEL clause.
      # This includes REMs, CREATE TABLE, column definitions, etc.
      $started++    if $line =~ /^PARALLEL/;
      next LINE     if not $started;

      # Set $done when we hit a semicolon
      $done = $line =~ s/\;$//;

      # But keep everything in between
      push @lines_out, $line    unless $line =~ /^$/;

      # Exit when we get to the semicolon and ignore the rest.
      # This eliminates the ';' and any COMMENTs.
      last LINE if $done;
    }

  my $sql = join "\n", @lines_out;
  
  $isasnaptabl = 0;

  return $sql .  "\n";
}

# sub _create_package
#
# Returns DDL to create the named package in the form of:
#
#     CREATE OR REPLACE PACKAGE [schema.]<name>
#     AS
#     <source>
# 
# by calling _display_source
#
sub _create_package
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql =  _display_source( $schema, $owner, $name, $view, 'PACKAGE' );

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql ;
}

# sub _create_package_body
#
# Returns DDL to create the named procedure in the form of:
#
#     CREATE OR REPLACE PACKAGE BODY [schema.]<name>
#     AS
#     <source>
# 
# by calling _display_source
#
sub _create_package_body
{
  return _display_source( @_, 'PACKAGE BODY' );
}

# sub _create_partitioned_index
#
# Creates the GLOBAL/LOCAL partition syntax part of a CREATE INDEX statement.
#
sub _create_partitioned_index
{
  my ( $schema, $owner, $name, $view, $sql ) = @_;

  my $stmt;

  if ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            -- 8.0 Indexes may partition only by RANGE
                            i.partitioning_type
                        , 'N/A'                         AS subpartitioning_type
                        , i.locality
                        , 0                             AS compressed
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , i.def_pct_free
                        , DECODE(
                                            i.def_ini_trans
                                          ,0,1
                                          ,null,1
                                          ,i.def_ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            i.def_max_trans
                                          ,0,255
                                          ,null,255
                                          ,i.def_max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        ,DECODE(
                                          i.def_initial_extent
                                        ,'DEFAULT',s.initial_extent
                                        ,i.def_initial_extent * $block_size * 1024
                                      )                        AS initial_extent
                        ,DECODE(
                                          i.def_next_extent
                                        ,'DEFAULT',s.next_extent
                                        ,i.def_next_extent * $block_size * 1024
                                      )                        AS next_extent
                        , DECODE(
                                            i.def_min_extents
                                          ,'DEFAULT',s.min_extents
                                          ,i.def_min_extents
                                        )                       AS min_extents
                        , DECODE(
                                            i.def_max_extents
                                          ,'DEFAULT',DECODE(
                                                                                s.max_extents
                                                                              ,2147483645,'unlimited'
                                                                              ,s.max_extents
                                                                            )
                                          ,2147483645,'unlimited'
                                          ,i.def_max_extents
                                        )                       AS max_extents
                        , DECODE(
                                            i.def_pct_increase
                                          ,'DEFAULT',s.pct_increase
                                          ,i.def_pct_increase
                                        )                       AS pct_increase
                        , DECODE(
                                            i.def_freelists
                                          ,0,1
                                          ,null,1
                                          ,i.def_freelists
                                        )                       AS freelists
                        , DECODE(
                                            i.def_freelist_groups
                                          ,0,1
                                          ,null,1
                                          ,i.def_freelist_groups
                                        )                       AS freelist_groups
                        , 'N/A'                         AS buffer_pool
                        , DECODE(
                                            i.def_logging
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(NVL(i.def_tablespace_name,s.tablespace_name))
                            -- Don't have default blocks, so use larger of initial/next
                        , GREATEST(
                                                DECODE(
                                                                i.def_initial_extent
                                                              ,'DEFAULT',s.initial_extent / $block_size / 1024
                                                              ,i.def_initial_extent
                                                            )
                                              ,DECODE(
                                                                i.def_next_extent
                                                              ,'DEFAULT',s.next_extent / $block_size / 1024
                                                              ,i.def_next_extent
                                                            )
                                            )                     AS blocks
              FROM
                            ${view}_part_indexes  i
                        , ${view}_indexes       n
                        , ${view}_tablespaces   s
                        , ${view}_part_tables   t
              WHERE
                                    -- def_tablspace is sometimes NULL in PART_INDEXES,
                                    -- we'll have to go over to the table for the defaults
                                    i.index_name      = UPPER( ? )
                            AND n.index_name      = UPPER( ? )
                            AND t.table_name      = n.table_name
                            AND s.tablespace_name = t.def_tablespace_name
            ";
  }
  else               # We're Oracle8i or newer
  {
    $stmt =
      "
              SELECT
                            -- Indexes may partition only by RANGE or RANGE/HASH
                            i.partitioning_type
                        , i.subpartitioning_type
                        , i.locality
                        , DECODE(
                                            n.compression
                                          ,'ENABLED',n.prefix_length
                                          ,0
                                        )                             AS compressed
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , i.def_pct_free
                        , DECODE(
                                            i.def_ini_trans
                                          ,0,1
                                          ,null,1
                                          ,i.def_ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            i.def_max_trans
                                          ,0,255
                                          ,null,255
                                          ,i.def_max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        ,DECODE(
                                          i.def_initial_extent
                                        ,'DEFAULT',s.initial_extent
                                        ,i.def_initial_extent * $block_size * 1024
                                      )                        AS initial_extent
                        ,DECODE(
                                          i.def_next_extent
                                        ,'DEFAULT',s.next_extent
                                        ,i.def_next_extent * $block_size * 1024
                                      )                        AS next_extent
                        , DECODE(
                                            i.def_min_extents
                                          ,'DEFAULT',s.min_extents
                                          ,i.def_min_extents
                                        )                       AS min_extents
                        , DECODE(
                                            i.def_max_extents
                                          ,'DEFAULT',DECODE(
                                                                                s.max_extents
                                                                              ,2147483645,'unlimited'
                                                                              ,s.max_extents
                                                                            )
                                          ,2147483645,'unlimited'
                                          ,i.def_max_extents
                                        )                       AS max_extents
                        , DECODE(
                                            i.def_pct_increase
                                          ,'DEFAULT',s.pct_increase
                                          ,i.def_pct_increase
                                        )                       AS pct_increase
                        , DECODE(
                                            i.def_freelists
                                          ,0,1
                                          ,null,1
                                          ,i.def_freelists
                                        )                       AS freelists
                        , DECODE(
                                            i.def_freelist_groups
                                          ,0,1
                                          ,null,1
                                          ,i.def_freelist_groups
                                        )                       AS freelist_groups
                        , LOWER(i.def_buffer_pool)        AS buffer_pool
                        , DECODE(
                                            i.def_logging
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(NVL(i.def_tablespace_name,s.tablespace_name))
                            -- Don't have default blocks, so use larger of initial/next
                        , GREATEST(
                                                DECODE(
                                                                i.def_initial_extent
                                                              ,'DEFAULT',s.initial_extent / $block_size / 1024
                                                              ,i.def_initial_extent
                                                            )
                                              ,DECODE(
                                                                i.def_next_extent
                                                              ,'DEFAULT',s.next_extent / $block_size / 1024
                                                              ,i.def_next_extent
                                                            )
                                            )                     AS blocks
              FROM
                            ${view}_part_indexes  i
                        , ${view}_indexes       n
                        , ${view}_tablespaces   s
                        , ${view}_part_tables   t
              WHERE
                                    -- def_tablspace is sometimes NULL in PART_INDEXES,
                                    -- we'll have to go over to the table for the defaults
                                    i.index_name      = UPPER( ? )
                            AND n.index_name      = UPPER( ? )
                            AND t.table_name      = i.table_name
                            AND s.tablespace_name = t.def_tablespace_name
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND i.owner           = UPPER('$owner')
                            AND n.owner           = UPPER('$owner')
                            AND t.owner           = UPPER('$owner')
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name, $name );
  my @row = $sth->fetchrow_array;

  my $partitioning_type      = shift @row;
  my $subpartitioning_type   = shift @row;
  my $locality               = shift @row;
  my $compressed             = shift @row;

  unshift @row, ( '' );    #indent (none)

  $sql .= _segment_attributes( \@row );

  if ( $compressed )
  {
    $sql .= "COMPRESS            $compressed\n";
  }

  if ( $locality eq 'GLOBAL' )
  {
    $sql .= "GLOBAL PARTITION BY RANGE\n" . # Only RANGE on global indexes
            "(\n    " .
            _partition_key_columns( $owner, $name, 'INDEX', $view ) .
            "\n)\n" .
            _range_partitions($owner, $name, $view,
                              $subpartitioning_type, 'GLOBAL' );
  }
  else     # Must be partitione by RANGE or RANGE/HASH
  {
    $sql .= "LOCAL\n";

    if ( $partitioning_type eq 'RANGE' )
    {
      $sql .= _range_partitions( $owner, $name, $view,
                                 $subpartitioning_type, 'LOCAL' );
    }
  }

  return $sql;
}

# sub _create_partitioned_iot
#
# Returns DDL to create the partitioned index organized table 
# and its partition(s).
# See _create_table for format.
#
sub _create_partitioned_iot
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $stmt;

  if ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            -- Table Properties
                            'N/A'                         AS monitoring
                        , t.table_name
                            -- Parallel Clause
                        , LTRIM(t.degree)               AS degree
                        , LTRIM(t.instances)            AS instances
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , DECODE(
                                            LTRIM(t.cache)
                                          ,'Y','CACHE'
                                          ,    'NOCACHE'
                                        )                       AS cache
                        , 'N/A'                         AS pct_used
                        , p.def_pct_free                AS pct_free
                        , p.def_ini_trans               AS ini_trans
                        , p.def_max_trans               AS max_trans
                            -- Storage Clause
                        ,DECODE(
                                          p.def_initial_extent
                                        ,'DEFAULT',s.initial_extent
                                        ,p.def_initial_extent * $block_size * 1024
                                      )                        AS initial_extent
                        ,DECODE(
                                          p.def_next_extent
                                        ,'DEFAULT',s.next_extent
                                        ,p.def_next_extent * $block_size * 1024
                                      )                        AS next_extent
                        , DECODE(
                                            p.def_min_extents
                                          ,'DEFAULT',s.min_extents
                                          ,p.def_min_extents
                                        )                       AS min_extents
                        , DECODE(
                                            p.def_max_extents
                                          ,'DEFAULT',DECODE(
                                                                                s.max_extents
                                                                              ,2147483645,'unlimited'
                                                                              ,s.max_extents
                                                                            )
                                          ,2147483645,'unlimited'
                                          ,p.def_max_extents
                                        )                       AS max_extents
                        , DECODE(
                                            p.def_pct_increase
                                          ,'DEFAULT',s.pct_increase
                                          ,p.def_pct_increase
                                        )                       AS pct_increase
                        , DECODE(
                                            p.def_freelists
                                          ,0,1
                                          ,NVL(p.def_freelists,1)
                                        )                       AS freelists
                        , DECODE(
                                            p.def_freelist_groups
                                          ,0,1
                                          ,NVL(p.def_freelist_groups,1)
                                        )                       AS freelist_groups
                        , 'N/A'                         AS buffer_pool
                        , DECODE(
                                            p.def_logging 
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(p.def_tablespace_name)  AS tablespace_name
                        , t.blocks - NVL(t.empty_blocks,0)
              FROM
                            ${view}_all_tables    t
                        , ${view}_part_indexes  p
                        , ${view}_tablespaces   s
              WHERE
                                    t.table_name      = UPPER('$name')
                            AND p.table_name      = t.table_name
                            AND s.tablespace_name = p.def_tablespace_name
            ";
  }
  else               # We're Oracle8i or newer
  {
    $stmt =
      "
              SELECT
                            -- Table Properties
                            DECODE(
                                            t.monitoring
                                          ,'NO','NOMONITORING'
                                          ,     'MONITORING'
                                        )                       AS monitoring
                        , t.table_name
                            -- Parallel Clause
                        , LTRIM(t.degree)               AS degree
                        , LTRIM(t.instances)            AS instances
                            -- Physical Properties
                        , 'INDEX'                       AS organization
                            -- Segment Attributes
                        , DECODE(
                                            LTRIM(t.cache)
                                          ,'Y','CACHE'
                                          ,    'NOCACHE'
                                        )                       AS cache
                        , 'N/A'                         AS pct_used
                        , p.def_pct_free                AS pct_free
                        , p.def_ini_trans               AS ini_trans
                        , p.def_max_trans               AS max_trans
                            -- Storage Clause
                        ,DECODE(
                                          p.def_initial_extent
                                        ,'DEFAULT',s.initial_extent
                                        ,p.def_initial_extent * $block_size * 1024
                                      )                        AS initial_extent
                        ,DECODE(
                                          p.def_next_extent
                                        ,'DEFAULT',s.next_extent
                                        ,p.def_next_extent * $block_size * 1024
                                      )                        AS next_extent
                        , DECODE(
                                            p.def_min_extents
                                          ,'DEFAULT',s.min_extents
                                          ,p.def_min_extents
                                        )                       AS min_extents
                        , DECODE(
                                            p.def_max_extents
                                          ,'DEFAULT',DECODE(
                                                                                s.max_extents
                                                                              ,2147483645,'unlimited'
                                                                              ,s.max_extents
                                                                            )
                                          ,2147483645,'unlimited'
                                          ,p.def_max_extents
                                        )                       AS max_extents
                        , DECODE(
                                            p.def_pct_increase
                                          ,'DEFAULT',s.pct_increase
                                          ,p.def_pct_increase
                                        )                       AS pct_increase
                        , DECODE(
                                            p.def_freelists
                                          ,0,1
                                          ,NVL(p.def_freelists,1)
                                        )                       AS freelists
                        , DECODE(
                                            p.def_freelist_groups
                                          ,0,1
                                          ,NVL(p.def_freelist_groups,1)
                                        )                       AS freelist_groups
                        , LOWER(p.def_buffer_pool)      AS buffer_pool
                        , DECODE(
                                            p.def_logging 
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(p.def_tablespace_name)  AS tablespace_name
                        , t.blocks - NVL(t.empty_blocks,0)
              FROM
                            ${view}_all_tables    t
                        , ${view}_part_indexes  p
                        , ${view}_tablespaces   s
              WHERE
                                    t.table_name      = UPPER('$name')
                            AND p.table_name      = t.table_name
                            AND s.tablespace_name = p.def_tablespace_name
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND t.owner           = UPPER('$owner')
                            AND p.owner           = t.owner 
            ";
  }

  my $sql = _create_table_text( $stmt, $schema, $owner, $name, $view );

  $stmt =
      "
              SELECT
                            constraint_name
              FROM
                            ${view}_constraints
              WHERE
                                    table_name      = UPPER( ? )
                            AND constraint_type = 'P'
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner           = UPPER('$owner')
            ";
  }

  my $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my ( $index ) = $sth->fetchrow_array;

  $sql .= "PARTITION BY RANGE\n" .  # Only RANGE allowed on IOT's
          "(\n    " .
          _partition_key_columns( $owner, $name, 'TABLE', $view ) .
          "\n)\n" .
          _range_partitions( $owner, $index, $view, 'NONE', 'IOT' ) .
          _create_comments( $schema, $owner, $name, $view );

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql;
}

# sub _create_partitioned_table
#
# Returns DDL to create the partitioned table and its partition(s).
# See _create_table for format.
#
sub _create_partitioned_table
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $stmt;

  if ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            -- Table Properties
                            'N/A'                         AS monitoring
                        , t.table_name
                            -- Parallel Clause
                        , LTRIM(t.degree)               AS degree
                        , LTRIM(t.instances)            AS instances
                            -- Physical Properties
                        , DECODE(
                                            t.iot_type
                                          ,'IOT','INDEX'
                                          ,      'HEAP'
                                        )                       AS organization
                            -- Segment Attributes
                        , DECODE(
                                            LTRIM(t.cache)
                                          ,'Y','CACHE'
                                          ,    'NOCACHE'
                                        )                       AS cache
                        , p.def_pct_used
                        , p.def_pct_free                AS pct_free
                        , p.def_ini_trans               AS ini_trans
                        , p.def_max_trans               AS max_trans
                            -- Storage Clause
                        ,DECODE(
                                          p.def_initial_extent
                                        ,'DEFAULT',s.initial_extent
                                        ,p.def_initial_extent * $block_size * 1024
                                      )                        AS initial_extent
                        ,DECODE(
                                          p.def_next_extent
                                        ,'DEFAULT',s.next_extent
                                        ,p.def_next_extent * $block_size * 1024
                                      )                        AS next_extent
                        , DECODE(
                                            p.def_min_extents
                                          ,'DEFAULT',s.min_extents
                                          ,p.def_min_extents
                                        )                       AS min_extents
                        , DECODE(
                                            p.def_max_extents
                                          ,'DEFAULT',DECODE(
                                                                                s.max_extents
                                                                              ,2147483645,'unlimited'
                                                                              ,s.max_extents
                                                                            )
                                          ,2147483645,'unlimited'
                                          ,p.def_max_extents
                                        )                       AS max_extents
                        , DECODE(
                                            p.def_pct_increase
                                          ,'DEFAULT',s.pct_increase
                                          ,p.def_pct_increase
                                        )                       AS pct_increase
                        , DECODE(
                                            p.def_freelists
                                          ,0,1
                                          ,NVL(p.def_freelists,1)
                                        )                       AS freelists
                        , DECODE(
                                            p.def_freelist_groups
                                          ,0,1
                                          ,NVL(p.def_freelist_groups,1)
                                        )                       AS freelist_groups
                        , 'N/A'                         AS buffer_pool
                        , DECODE(
                                            p.def_logging 
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(p.def_tablespace_name)  AS tablespace_name
                        , t.blocks - NVL(t.empty_blocks,0)
              FROM
                            ${view}_all_tables   t
                        , ${view}_part_tables  p
                        , ${view}_tablespaces  s
              WHERE
                                    t.table_name      = UPPER('$name')
                            AND p.table_name      = t.table_name
                            AND s.tablespace_name = p.def_tablespace_name
            ";
  }
  else               # We're Oracle8i or newer
  {
    $stmt =
      "
              SELECT
                            -- Table Properties
                            DECODE(
                                            t.monitoring
                                          ,'NO','NOMONITORING'
                                          ,     'MONITORING'
                                        )                       AS monitoring
                        , t.table_name
                            -- Parallel Clause
                        , LTRIM(t.degree)               AS degree
                        , LTRIM(t.instances)            AS instances
                            -- Physical Properties
                        , DECODE(
                                            t.iot_type
                                          ,'IOT','INDEX'
                                          ,      'HEAP'
                                        )                       AS organization
                            -- Segment Attributes
                        , DECODE(
                                            LTRIM(t.cache)
                                          ,'Y','CACHE'
                                          ,    'NOCACHE'
                                        )                       AS cache
                        , p.def_pct_used
                        , p.def_pct_free                AS pct_free
                        , p.def_ini_trans               AS ini_trans
                        , p.def_max_trans               AS max_trans
                            -- Storage Clause
                        ,DECODE(
                                          p.def_initial_extent
                                        ,'DEFAULT',s.initial_extent
                                        ,p.def_initial_extent * $block_size * 1024
                                      )                        AS initial_extent
                        ,DECODE(
                                          p.def_next_extent
                                        ,'DEFAULT',s.next_extent
                                        ,p.def_next_extent * $block_size * 1024
                                      )                        AS next_extent
                        , DECODE(
                                            p.def_min_extents
                                          ,'DEFAULT',s.min_extents
                                          ,p.def_min_extents
                                        )                       AS min_extents
                        , DECODE(
                                            p.def_max_extents
                                          ,'DEFAULT',DECODE(
                                                                                s.max_extents
                                                                              ,2147483645,'unlimited'
                                                                              ,s.max_extents
                                                                            )
                                          ,2147483645,'unlimited'
                                          ,p.def_max_extents
                                        )                       AS max_extents
                        , DECODE(
                                            p.def_pct_increase
                                          ,'DEFAULT',s.pct_increase
                                          ,p.def_pct_increase
                                        )                       AS pct_increase
                        , DECODE(
                                            p.def_freelists
                                          ,0,1
                                          ,NVL(p.def_freelists,1)
                                        )                       AS freelists
                        , DECODE(
                                            p.def_freelist_groups
                                          ,0,1
                                          ,NVL(p.def_freelist_groups,1)
                                        )                       AS freelist_groups
                        , LOWER(p.def_buffer_pool)      AS buffer_pool
                        , DECODE(
                                            p.def_logging 
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(p.def_tablespace_name)  AS tablespace_name
                        , t.blocks - NVL(t.empty_blocks,0)
              FROM
                            ${view}_all_tables   t
                        , ${view}_part_tables  p
                        , ${view}_tablespaces  s
              WHERE
                                    t.table_name      = UPPER('$name')
                            AND p.table_name      = t.table_name
                            AND s.tablespace_name = p.def_tablespace_name
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND t.owner           = UPPER('$owner')
                            AND p.owner           = t.owner 
            ";
  }

  my $sql = _create_table_text( $stmt, $schema, $owner, $name, $view );

  $sql =~ /ORGANIZATION\s+(\w+)/gm;
  my $organization = $1;

  if ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            partitioning_type
                        , partition_count
                        , 'N/A'                        AS subpartitioning_type
                        , 'N/A'                        AS def_subpartition_count
              FROM
                            ${view}_part_tables
              WHERE
                                    table_name = UPPER( ? )
            ";
  }
  else               # We're Oracle8i or newer
  {
    $stmt =
      "
              SELECT
                            partitioning_type
                        , partition_count
                        , subpartitioning_type
                        , def_subpartition_count
              FROM
                            ${view}_part_tables
              WHERE
                                    table_name = UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner      = UPPER('$owner')
            ";
  }

  my $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my (
       $partitioning_type,
       $partition_count,
       $subpartitioning_type,
       $subpartition_count
      ) = $sth->fetchrow_array;

  $sql .= "PARTITION BY $partitioning_type\n" .
          "(\n    " .
          _partition_key_columns( $owner, $name, 'TABLE', $view ) .
          "\n)\n";

  if ( $partitioning_type eq 'RANGE' )
  {
    if ( $subpartitioning_type eq 'HASH' )
    {
      $sql .= "SUBPARTITION BY HASH\n" .
              "(\n    " .
              _subpartition_key_columns( $owner, $name, 'TABLE', $view ) .
              "\n)\n" .
              "SUBPARTITIONS $subpartition_count" .
              "\n";
    }

    $sql .= "(\n";

    if ( $oracle_major == 8 and $oracle_minor == 0 )
    {
       $stmt =
      "
              SELECT
                            partition_name
                        , high_value
                        , 'N/A'
                        , pct_used
                        , pct_free
                        , ini_trans
                        , max_trans
                            -- Storage Clause
                        , initial_extent
                        , next_extent
                        , min_extent
                        , DECODE(
                                            max_extent
                                          ,2147483645,'unlimited'
                                          ,           max_extent
                                        )                       AS max_extents
                        , pct_increase
                        , NVL(freelists,1)
                        , NVL(freelist_groups,1)
                        , 'N/A'                         AS buffer_pool
                        , DECODE(
                                            logging 
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(tablespace_name)
                        , blocks - NVL(empty_blocks,0)
              FROM
                            ${view}_tab_partitions
              WHERE
                                    table_name =  UPPER( ? )
            ";
    }
    else               # We're Oracle8i or newer
    {
      $stmt =
      "
              SELECT
                            partition_name
                        , high_value
                        , 'N/A'
                        , pct_used
                        , pct_free
                        , ini_trans
                        , max_trans
                            -- Storage Clause
                        , initial_extent
                        , next_extent
                        , min_extent
                        , DECODE(
                                            max_extent
                                          ,2147483645,'unlimited'
                                          ,           max_extent
                                        )                       AS max_extents
                        , pct_increase
                        , NVL(freelists,1)
                        , NVL(freelist_groups,1)
                        , LOWER(buffer_pool)
                        , DECODE(
                                            logging 
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(tablespace_name)
                        , blocks - NVL(empty_blocks,0)
              FROM
                            ${view}_tab_partitions
              WHERE
                                    table_name =  UPPER( ? )
            ";
    }

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                            AND table_owner = UPPER('$owner')
            ";
    }

    $stmt .=
      "
              ORDER
                    BY
                            partition_name
            ";

    $dbh->{ LongReadLen } = 8192;    # Allows HIGH_VALUE length of 8K
    $dbh->{ LongTruncOk } = 1;

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my $aref = $sth->fetchall_arrayref;

    my $comma = '    ';

    foreach my $row ( @$aref )
    {
      my $partition  = shift @$row;
      my $high_value = shift @$row;

      $sql .= "${comma}PARTITION \L$partition \UVALUES LESS THAN\n" .
              "      (\n" .
              "        $high_value\n" .
              "      )\n";

      unshift @$row, ( '      ', $organization );

      $sql .= _segment_attributes( $row );

      $comma = '  , ';

      if ( $subpartitioning_type eq 'HASH' )
      {
        $stmt =
          "
                      SELECT
                                    subpartition_name
                                , tablespace_name
                      FROM
                                    ${view}_tab_subpartitions
                      WHERE
                                            table_name     =  UPPER( ? )
                                    AND partition_name = '$partition'
                    ";

        if ( $view eq 'DBA' )
        {
          $stmt .=
          "
                                    AND table_owner    = UPPER('$owner')
                    ";
        }

        $stmt .=
          "
                      ORDER
                            BY
                                    subpartition_name
                    ";

        $sth = $dbh->prepare( $stmt );
        $sth->execute( $name );
        my $aref = $sth->fetchall_arrayref;

        $sql .= "        (\n            ";

        my @cols;
        foreach my $row ( @$aref )
        {
          push @cols, "SUBPARTITION \L$row->[0] \UTABLESPACE \L$row->[1]";
        }
        $sql .= join ( "\n          , ", @cols );

        $sql .= "\n        )\n";
      }
    }
    $sql .= ");\n\n";
  }
  else   # It's HASH partitioning
  {
    $sql .= "(\n    ";

    $stmt =
      "
              SELECT
                            partition_name
                        , tablespace_name
              FROM
                            ${view}_tab_partitions
              WHERE
                                    table_name =  UPPER( ? )
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                            AND table_owner= UPPER('$owner')
            ";
    }

    $stmt .=
      "
              ORDER
                    BY
                            partition_name
            ";

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my $aref = $sth->fetchall_arrayref;

    my @cols;
    foreach my $row ( @$aref )
    {
      push @cols, "PARTITION \L$row->[0] \UTABLESPACE \L$row->[1]";
    }
    $sql .= join ( "\n  , ", @cols );

    $sql .= "\n) ;\n\n";
  }

  $sql .= _create_comments( $schema, $owner, $name, $view );

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql ;
}

# sub _create_procedure
#
# Returns DDL to create the named procedure in the form of:
#
#     CREATE OR REPLACE PROCEDURE [schema.]<name>
#     AS
#     <source>
# 
# by calling _display_source
#
sub _create_procedure
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql =  _display_source( $schema, $owner, $name, $view, 'PROCEDURE' );

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql ;
}

# sub _create_profile
#
# Returns DDL to create the named profile in the form of:
#
#     CREATE PROFILE <name>
#     LIMIT
#       SESSIONS_PER_USER   <value>
#       CPU_PER_SESSION     <value>
#       CPU_PER_CALL        <value>
#       etc
#
sub _create_profile
{
  my ( $schema, $owner, $name, $view ) = @_;

  $sth = $dbh->prepare(
      "
              SELECT
                            RPAD(resource_name,27)
                        , DECODE(
                                            RESOURCE_NAME
                                          ,'PASSWORD_VERIFY_FUNCTION',DECODE(
                                                                                                                  limit
                                                                                                                ,'UNLIMITED','null'
                                                                                                                ,LOWER(limit)
                                                                                                              )
                                          ,                           LOWER(limit)
                                        )
              FROM
                            dba_profiles
              WHERE
                            profile = UPPER( ? )
              ORDER
                    BY
                            DECODE(
                                            SUBSTR(resource_name,1,8)
                                          ,'FAILED_L',2
                                          ,'PASSWORD',2
                                          ,1
                                        )
                        , resource_name
            ");

  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;
  die "\nProfile '\U$name' \Ldoes not exist.\n\n" unless @$aref;

  my $sql = "PROMPT " .
            "CREATE PROFILE \L$name\n\n" .
            "CREATE PROFILE \L$name\n" .
            "LIMIT\n";

  foreach my $row ( @$aref )
  {
    $sql .= "   $row->[0]$row->[1]\n";
  }

  $sql .= ";\n\n";

  return $sql;
}

# sub _create_role
#
# Returns DDL to create the named role in the form of:
#
#     CREATE ROLE <name> IDENTIFIED {EXTERNALLY|BY VALUES '<values>'}
#     or
#     CREATE ROLE <name> NOT IDENTIFIED
# 
sub _create_role
{
  my ( $schema, $owner, $name, $view ) = @_;

  die "\nYou must use the DBA views in order to CREATE ROLE\n\n"
      unless $view eq 'DBA';

  my $stmt =
      "
              SELECT
                            DECODE(
                                            r.password_required
                                          ,'YES', DECODE(
                                                                          u.password
                                                                        ,'EXTERNAL','IDENTIFIED EXTERNALLY'
                                                                        ,'IDENTIFIED BY VALUES ''' 
                                                                            || u.password || ''''
                                                                      )
                                          ,'NOT IDENTIFIED'
                                        )                         AS password
              FROM
                            dba_roles   r
                        , sys.user\$  u
              WHERE
                                    r.role = UPPER( ? )
                            AND u.name = UPPER( ? )
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name, $name );
  my @row = $sth->fetchrow_array;
  die "\nRole \U$name \Ldoes not exist.\n\n" unless @row;

  my ( $password ) = @row;

  my $sql  =
          "PROMPT " .
          "CREATE ROLE \L$name\n\n" .
          "CREATE ROLE \L$name \U$password;\n\n";

  $sql .= _granted_privs( $name );

  return $sql;
}

# sub _create_rollback_segment
#
# Returns DDL to create the named rollback segment in the form of:
#
#     CREATE [PUBLIC] ROLLBACK SEGMENT <name>
#     STORAGE
#     (
#       storage clause
#     )
#     TABLESPACE tablespace
#
sub _create_rollback_segment
{
  my ( $schema, $owner, $name, $view ) = @_;

  $sth = $dbh->prepare(
      "
              SELECT
                            DECODE(
                                            r.owner
                                          ,'PUBLIC',' PUBLIC '
                                          ,         ' '
                                        )                                  AS is_public
                        , r.tablespace_name
                        , NVL(r.initial_extent,t.initial_extent)   AS initial_extent
                        , NVL(r.next_extent,t.next_extent)         AS next_extent
                        , r.min_extents
                        , DECODE(
                                            r.max_extents
                                          ,2147483645,'unlimited'
                                          ,           r.max_extents
                                        )                                  AS max_extents
              FROM
                            dba_rollback_segs    r
                        , ${view}_tablespaces  t
              WHERE
                                    r.segment_name    = UPPER( ? )
                            AND t.tablespace_name = r.tablespace_name
            ");

  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  die "\nRollback Segment '\U$name' \Ldoes not exist.\n\n" unless @row;

  my (
       $is_public,
       $tablespace_name,
       $initial_extent,
       $next_extent,
       $min_extents,
       $max_extents,
     ) = @row;

  return "PROMPT " .
         "CREATE${is_public}ROLLBACK SEGMENT \L$name\n\n" .
         "CREATE${is_public}ROLLBACK SEGMENT \L$name\n" .
         "STORAGE\n" .
         "(\n" .
         "  INITIAL      $initial_extent\n" .
         "  NEXT         $next_extent\n" .
         "  MINEXTENTS   $min_extents\n" .
         "  MAXEXTENTS   $max_extents\n" .
         ")\n" .
         "TABLESPACE     \L$tablespace_name\n" .
         ";\n\n" ; 
}

# sub _create_schema
#
# Returns DDL to create all objects of the following type:
#
#     TYPE
#     TABLE
#     INDEX
#     CONSTRAINT
#     TRIGGER
#     VIEW
#     SNAPSHOT
#     SNAPSHOT LOG
#     SYNONYM
#     DATABASE LINK
#     FUNCTION
#     PROCEDURE
#     PACKAGE
#     PACKAGE BODY
#     SEQUENCE
# 
sub _create_schema
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql;
  my $aref;
  my $stmt;

  #
  # Start with schema's types (they may be a data type in CREATE TABLE)
  #
  $stmt =
      "
              SELECT
                            object_name
              FROM
                            ${view}_objects
              WHERE
                                    object_type = 'TYPE'
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner       = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            object_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_type( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's tables
  #
  $stmt =
      "
              SELECT
                            table_name
              FROM
                            ${view}_tables
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE
                            owner = UPPER('$owner')
            ";
  }

  if ( $oracle_major > 7 )
  {
    $stmt .=
      "
              MINUS
              SELECT
                            table_name
              FROM
                            ${view}_tables
              WHERE
                                    iot_type = 'IOT_OVERFLOW'
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
        "
                            AND owner    = UPPER('$owner')
                ";
    }
  }

  $stmt .=
      "
              MINUS
              SELECT
                            log_table
              FROM
                            ${view}_snapshot_logs
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE  log_owner  = UPPER('$owner')
            ";
  }

  $stmt .=
      "
              MINUS
              SELECT
                            table_name
              FROM
                            ${view}_snapshots
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE
                            owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            1
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_table( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's indexes
  #

  # If we're an IOT (Oracle8 or newer), skip the PK index
  # because it was in the CREATE TABLE

  if ( $oracle_major == 7 )
  {
    $stmt =
      "
              SELECT
                            index_name
              FROM
                            ${view}_indexes  i
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
              WHERE
                            i.table_owner = UPPER('$owner')
            ";
    }
  }
  else   # we're Oracle8 or newer
  {
  $stmt =
      "
              SELECT
                            index_name
              FROM
                            ${view}_indexes  i
                        , ${view}_tables   t
              WHERE
                                    i.table_owner = UPPER('$owner')
                            AND t.table_name  = i.table_name
                            AND (
                                              t.iot_type <> 'IOT'
                                        OR (
                                                  NOT EXISTS (
                                                                            SELECT
                                                                                          null
                                                                            FROM
                                                                                          ${view}_constraints
                                                                            WHERE
                                                                                                  constraint_type = 'P'
                                                                                          AND constraint_name =
                                                                                                  i.index_name
                                                                                          AND owner           =
                                                                                                  i.table_owner
                                                                        )
                                              )
                                    )
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                            AND t.owner       = i.table_owner
            ";
    }
  }

  $stmt .= 
      "
              ORDER
                    BY
                            i.table_name
                        , i.index_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_index( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's constraints
  #

  # If we're an IOT (Oracle8 or newer), skip the PK constraint
  # because it was in the CREATE TABLE

  if ( $oracle_major == 7 )
  {
    $stmt =
      "
              SELECT
                            c.constraint_name
                        , c.constraint_type
                        , c.search_condition
              FROM
                            ${view}_constraints  c
              WHERE
                                    constraint_type IN('P','U','R','C')
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                            AND c.owner = UPPER('$owner')
            ";
    }
  }
  else   # we're Oracle8 or newer
  {
    $stmt =
      "
              SELECT
                            c.constraint_name
                        , c.constraint_type
                        , c.search_condition
              FROM
                            ${view}_constraints  c
                        , ${view}_tables       t
              WHERE
                                    t.table_name = c.table_name
                            AND (
                                              (
                                                          (
                                                                    t.iot_type <> 'IOT'
                                                              OR t.iot_type IS NULL
                                                          )
                                                  AND c.constraint_type IN('P','U','R','C')
                                              )
                                        OR (
                                                          t.iot_type         = 'IOT'
                                                  AND c.constraint_type IN ('U','R','C')
                                              )
                                    )
                            AND NOT EXISTS (
                                                              SELECT
                                                                            null
                                                              FROM
                                                                            ${view}_snapshot_logs
                                                              WHERE
                                                                                    log_table = c.table_name
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                                                                            AND log_owner = c.owner
            ";
    }

    $stmt .=
      "
                                                          )
                            AND NOT EXISTS (
                                                              SELECT
                                                                            null
                                                              FROM
                                                                            ${view}_snapshots
                                                              WHERE
                                                                                    table_name = c.table_name
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                                                                            AND owner      = c.owner
            ";
    }

    $stmt .=
      "
                                                          )
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                            AND c.owner      = UPPER('$owner')
                            AND t.owner      = c.owner
            ";
    }
  }

  $stmt .=
      "
              ORDER
                    BY
                            DECODE(
                                            c.constraint_type
                                          ,'P',1
                                          ,'U',2
                                          ,'R',3
                                          ,'C',4
                                        )
                        , c.table_name
                        , c.constraint_name
            ";

  $dbh->{ LongReadLen } = 8192;    # Allows SEARCH_CONDITION length of 8K
  $dbh->{ LongTruncOk } = 1;

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    my ( $cons_name, $cons_type, $condition ) = @$row;

    if ( $cons_type ne 'C' )
    {
      $sql .= _create_constraint( $schema, $owner, $cons_name, $view );
    }
    elsif ( $condition !~ /IS NOT NULL/ )
    {
      $sql .= _create_constraint( $schema, $owner, $cons_name, $view );
    }
  }

  #
  # Add schema's triggers
  #
  $stmt =
      "
              SELECT
                            trigger_name
              FROM
                            ${view}_triggers
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE
                            table_owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            table_name
                        , trigger_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_trigger( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's views
  #
  $stmt =
      "
              SELECT
                            view_name
              FROM
                            ${view}_views
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE
                            owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            view_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_view( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's snapshot logs
  #
  $stmt =
      "
              SELECT
                            master
              FROM
                            ${view}_snapshot_logs
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE  log_owner  = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            master
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_snapshot_log( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's snapshots
  #
  $stmt =
      "
              SELECT
                            name
              FROM
                            ${view}_snapshots
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE
                            owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_snapshot( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's synonyms
  #
  $stmt =
      "
              SELECT
                            synonym_name
              FROM
                            ${view}_synonyms
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE
                            owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            synonym_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_synonym( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's database links
  #
  $stmt =
      "
              SELECT
                            db_link
              FROM
                            ${view}_db_links
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE
                            owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            db_link
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_db_link( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's functions
  #
  $stmt =
      "
              SELECT
                            object_name
              FROM
                            ${view}_objects
              WHERE
                                    object_type = 'FUNCTION'
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner       = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            object_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_function( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's procedures
  #
  $stmt =
      "
              SELECT
                            object_name
              FROM
                            ${view}_objects
              WHERE
                                    object_type = 'PROCEDURE'
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner       = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            object_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_procedure( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's packages
  #
  $stmt =
      "
              SELECT
                            object_name
              FROM
                            ${view}_objects
              WHERE
                                    object_type = 'PACKAGE'
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner       = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            object_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_package( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's package bodies
  #
  $stmt =
      "
              SELECT
                            object_name
              FROM
                            ${view}_objects
              WHERE
                                    object_type = 'PACKAGE BODY'
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner       = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            object_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_package_body( $schema, $owner, @$row->[0], $view );
  }

  #
  # Add schema's sequences
  #
  $stmt =
      "
              SELECT
                            sequence_name
              FROM
                            ${view}_sequences
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
              WHERE
                            sequence_owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                            sequence_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_sequence( $schema, $owner, @$row->[0], $view );
  }

  $sql .= "PROMPT Recompile schema \U$owner\n\n" .
          "BEGIN dbms_utility.compile_schema('\U$owner') ; END ;\n/\n\n"
      unless $schema eq 'PUBLIC';

  return $sql;
}

# sub _create_sequence
#
# Returns DDL to create the named sequence in the form of:
#
#     CREATE SEQUENCE [schema.]<name>
#        START WITH     <integer>
#        INCREMENT BY   <integer>
#        [NO]MINVALUE   <integer>
#        [NO]MAXVALUE   <integer>
#        [NO]CACHE      <integer>
#        [NO]CYCLE
#        [NO]ORDER
# 
sub _create_sequence
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $stmt =
      "
              SELECT
                            'START WITH       '
                              || LTRIM(TO_CHAR(last_number + cache_size,'fm999999999'))
                                                                                              AS start_with
                        , 'INCREMENT BY     '
                              || LTRIM(TO_CHAR(increment_by,'fm999999999')) AS imcrement_by
                        , DECODE(
                                            min_value
                                          ,0,'NOMINVALUE'
                                          ,'MINVALUE         ' || TO_CHAR(min_value)
                                        )                          AS min_value
                        , DECODE(
                                            TO_CHAR(max_value,'fm999999999999999999999999999')
                                          ,'999999999999999999999999999','NOMAXVALUE'
                                          ,'MAXVALUE         ' || TO_CHAR(max_value)
                                        )                          AS max_value
                        , DECODE(
                                            cache_size
                                          ,0,'NOCACHE'
                                          ,'CACHE            ' || TO_CHAR(cache_size)
                                        )                          AS cache_size
                        , DECODE(
                                            cycle_flag
                                          ,'Y','CYCLE'
                                          ,'N', 'NOCYCLE'
                                        )                          AS cycle_flag
                        , DECODE(
                                            order_flag
                                          ,'Y','ORDER'
                                          ,'N', 'NOORDER'
                                        )                          AS order_flag
              FROM
                            ${view}_sequences
              WHERE
                                    sequence_name  = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND sequence_owner = UPPER('$owner')
                "; }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  die "\nSequence \U$name \Ldoes not exist.\n\n" unless @row;

  my (
       $start_with,
       $increment_by,
       $min_value,
       $max_value,
       $cache_size,
       $cycle_flag,
       $order_flag,
     ) = @row;

  my $sql = "PROMPT " .
            "CREATE SEQUENCE \L$schema$name\n\n" .
            "CREATE SEQUENCE \L$schema$name\n" .
            "   $start_with\n" .
            "   $increment_by\n" .
            "   $min_value\n" .
            "   $max_value\n" .
            "   $cache_size\n" .
            "   $cycle_flag\n" .
            "   $order_flag\n" .
            ";\n\n";

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql;
}

# sub _create_snapshot
#
# Returns DDL to create the named materialized view
# by calling _create_mview (which is shared with
# _create_materialized_view)
#
sub _create_snapshot
{
  _create_mview( @_, 'SNAPSHOT' );
}

# sub _create_snapshot_log
#
# Returns DDL to create the named snapshot log
# by calling _create_mview (which is shared with
# _create_materialized_log)
#
sub _create_snapshot_log
{
  _create_mview_log( @_, 'SNAPSHOT' );
}

# sub _create_synonym
#
# Returns DDL to create the named synonym in the form of:
#
#     CREATE [PUBLIC] SYNONYM <name> FOR [schema.]<object>[@dblink]
#
sub _create_synonym
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $stmt =
      "
              SELECT
                            table_owner
                        , table_name
                        , NVL(db_link,'NULL')
              FROM
                            ${view}_synonyms
              WHERE
                                    synonym_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND owner        = UPPER('$owner')
                ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  die "Synonym \U$name \Ldoes not exist.\n\n" unless @row;

  my ( $table_owner, $table_name, $db_link ) = @row;

  $db_link      = ( $db_link   eq 'NULL' )   ? ''        : "\@$db_link";
  $schema       = ( $schema    eq 'PUBLIC' ) ? ''        : $schema;
  my $is_public = ( "\U$owner" eq 'PUBLIC' ) ? ' PUBLIC' : '';
  my $table_schema = _set_schema( $table_owner );

  my $sql = "PROMPT " .
            "CREATE$is_public SYNONYM \L$schema$name " .
               "FOR \L$table_schema$table_name$db_link \n\n" .
            "CREATE$is_public SYNONYM \L$schema$name " .
               "FOR \L$table_schema$table_name$db_link;\n\n";

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $table_owner, $table_name, $view );
  }

  return $sql;
}

# sub _create_table
#
# Returns DDL to create the named table and its comments
# and its partition(s) in the form of:
#
#     CREATE TABLE [schema.]<name>
#     (
#       <column list>
#     )
#     ORGANIZATION {HEAP|INDEX}
#     [NO]MONIOTORING
#     PARALLEL 
#     ( 
#       DEGREE     <value> 
#       INSTANCES  <value> 
#     ) 
#     [NO]CACHE 
#     [PCTUSED]    <value>
#     PCTFREE      <value>
#     INITRANS     <value>
#     MAXTRANS     <value>
#     STORAGE
#     (
#       <storage clause>
#     )
#     [NO]LOGING
#     TABLESPACE   <name>
#     [<partitioning clause>]
#
sub _create_table
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql;
  my $stmt;

  if ( $oracle_major == 7 )
  {
    $stmt =
      "
              SELECT
                            'NO'                    AS partitioned
                        , 'NOT IOT'               AS iot_type
              FROM
                            ${view}_tables
              WHERE
                                    table_name = UPPER( ? )
            ";
  }
  else
  {
    $stmt =
      "
              SELECT
                            partitioned
                        , iot_type
              FROM
                            ${view}_tables
              WHERE
                                    table_name = UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND owner      = UPPER('$owner')
                ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  die "Table \U$name \Ldoes not exist.\n\n" unless @row;

  my ( $partitioned, $iot_type ) = @row;

  if ( $iot_type eq 'IOT' )
  {
    if ( $partitioned eq 'YES' )
    {
      return _create_partitioned_iot( $schema, $owner, $name, $view )
    }
    else
    {
      return _create_iot( $schema, $owner, $name, $view )
    }
  }
  elsif ( $partitioned eq 'YES' )
  {
    return _create_partitioned_table( $schema, $owner, $name, $view )
  }

  # We must be a plain, vanilla, non-partitioned, relational table.

  if ( $oracle_major == 7 )
  {
    $stmt =
      "
              SELECT
                            -- Table Properties
                            'N/A'                         AS monitoring
                        , 'N/A'                         AS table_name
                            -- Parallel Clause
                        , LTRIM(t.degree)
                        , LTRIM(t.instances)
                            -- Physical Properties
                        , 'N/A'                         AS organization
                            -- Segment Attributes
                        , DECODE(
                                            LTRIM(t.cache)
                                          ,'Y','CACHE'
                                          ,    'NOCACHE'
                                        )
                        , t.pct_used
                        , t.pct_free
                        , DECODE(
                                            t.ini_trans
                                          ,0,1
                                          ,null,1
                                          ,t.ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            t.max_trans
                                          ,0,255
                                          ,null,255
                                          ,t.max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        , t.initial_extent
                        , t.next_extent
                        , t.min_extents
                        , DECODE(
                                            t.max_extents
                                          ,2147483645,'unlimited'
                                          ,           t.max_extents
                                        )                       AS max_extents
                        , NVL(t.pct_increase,0)
                        , NVL(t.freelists,1)
                        , NVL(t.freelist_groups,1)
                        , 'N/A'                         AS buffer_pool
                        , 'N/A'                         AS logging
                        , LOWER(t.tablespace_name)      AS tablespace_name
                        , s.blocks - NVL(t.empty_blocks,0)
              FROM
                            ${view}_tables    t
                        , ${view}_segments  s
              WHERE
                                    t.table_name   = UPPER('$name')
                            AND t.table_name   = s.segment_name
            ";
  }
  elsif ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            -- Table Properties
                            'N/A'                         AS monitoring
                        , 'N/A'                         AS table_name
                            -- Parallel Clause
                        , LTRIM(t.degree)
                        , LTRIM(t.instances)
                            -- Physical Properties
                        , DECODE(
                                            t.iot_type
                                          ,'IOT','INDEX'
                                          ,      'HEAP'
                                        )                       AS organization
                            -- Segment Attributes
                        , DECODE(
                                            LTRIM(t.cache)
                                          ,'Y','CACHE'
                                          ,    'NOCACHE'
                                        )
                        , t.pct_used
                        , t.pct_free
                        , DECODE(
                                            t.ini_trans
                                          ,0,1
                                          ,null,1
                                          ,t.ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            t.max_trans
                                          ,0,255
                                          ,null,255
                                          ,t.max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        , t.initial_extent
                        , t.next_extent
                        , t.min_extents
                        , DECODE(
                                            t.max_extents
                                          ,2147483645,'unlimited'
                                          ,           t.max_extents
                                        )                       AS max_extents
                        , NVL(t.pct_increase,0)
                        , NVL(t.freelists,1)
                        , NVL(t.freelist_groups,1)
                        , 'N/A'                         AS buffer_pool
                        , DECODE(
                                            t.logging
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(t.tablespace_name)      AS tablespace_name
                        , s.blocks - NVL(t.empty_blocks,0)
              FROM
                            ${view}_tables    t
                        , ${view}_segments  s
              WHERE
                                    t.table_name   = UPPER('$name')
                            AND t.table_name   = s.segment_name
            ";
  }
  else                   # We're Oracle8i or newer
  {
    $stmt =
      "
              SELECT
                            -- Table Properties
                            DECODE(
                                            t.monitoring
                                          ,'NO','NOMONITORING'
                                          ,     'MONITORING'
                                        )                       AS monitoring
                        , 'N/A'                         AS table_name
                            -- Parallel Clause
                        , LTRIM(t.degree)
                        , LTRIM(t.instances)
                            -- Physical Properties
                        , DECODE(
                                            t.iot_type
                                          ,'IOT','INDEX'
                                          ,      'HEAP'
                                        )                       AS organization
                            -- Segment Attributes
                        , DECODE(
                                            LTRIM(t.cache)
                                          ,'Y','CACHE'
                                          ,    'NOCACHE'
                                        )
                        , t.pct_used
                        , t.pct_free
                        , DECODE(
                                            t.ini_trans
                                          ,0,1
                                          ,null,1
                                          ,t.ini_trans
                                        )                       AS ini_trans
                        , DECODE(
                                            t.max_trans
                                          ,0,255
                                          ,null,255
                                          ,t.max_trans
                                        )                       AS max_trans
                            -- Storage Clause
                        , t.initial_extent
                        , t.next_extent
                        , t.min_extents
                        , DECODE(
                                            t.max_extents
                                          ,2147483645,'unlimited'
                                          ,           t.max_extents
                                        )                       AS max_extents
                        , NVL(t.pct_increase,0)
                        , NVL(t.freelists,1)
                        , NVL(t.freelist_groups,1)
                        , LOWER(t.buffer_pool)          AS buffer_pool
                        , DECODE(
                                            t.logging
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , LOWER(t.tablespace_name)      AS tablespace_name
                        , s.blocks - NVL(t.empty_blocks,0)
              FROM
                            ${view}_tables    t
                        , ${view}_segments  s
              WHERE
                                    t.table_name   = UPPER('$name')
                            AND t.table_name   = s.segment_name
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND s.owner        = UPPER('$owner')
                            AND t.owner        = s.owner
            ";
  }

  $sql = _create_table_text( $stmt, $schema, $owner, $name, $view ) .
         ";\n\n" .
         _create_comments( $schema, $owner, $name, $view );

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql;
}

# sub _create_table_family
#
# Combines the CREATE TABLE statement with its "family" -- Comments,
# Triggers and Constraints
#
sub _create_table_family
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $stmt;
  my $sql = _create_table( @_ );

  # Add table's indexes
  if ( $oracle_major == 7 )
  {
    $stmt =
      "
              SELECT
                            'NOT IOT'               AS iot_type
              FROM
                            ${view}_tables
              WHERE
                                    table_name = UPPER( ? )
            ";
  }
  else
  {
    $stmt =
      "
              SELECT
                            iot_type
              FROM
                            ${view}_tables
              WHERE
                                    table_name = UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
        "
                            AND owner      = UPPER('$owner')
                ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my ( $iot ) = $sth->fetchrow_array;

  $stmt =
      "
              SELECT
                            index_name
              FROM
                            ${view}_indexes
              WHERE
                                    table_name  = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND table_owner = UPPER('$owner')
            ";
  }

  if ( $iot eq 'IOT' )
  {
    # Omit the PK, it's in the CREATE TABLE on an IOT
    $stmt .=
      "
              MINUS
              SELECT
                            constraint_name
              FROM
                            ${view}_constraints
              WHERE
                                    table_name      = UPPER( '$name' )
                            AND constraint_type = 'P'
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                            AND owner           = UPPER('$owner')
            ";
    }
  }

  $stmt .= 
      "
              ORDER
                    BY
                          1
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_index( $schema, $owner, @$row->[0], $view );
  }

  # Add table's constraints
  $stmt =
      "
              SELECT
                            constraint_name
                        , constraint_type
                        , search_condition
              FROM
                            ${view}_constraints cn
              WHERE
                                    owner            = UPPER( ? )
                            AND table_name       = UPPER( ? )
            ";

  if ( $iot eq 'IOT' )
  {
    $stmt .=
      "
                            AND constraint_type IN('U','R','C')
            ";
  }
  else
  {
    $stmt .=
      "
                            AND constraint_type IN('P','U','R','C')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                          DECODE(
                                          constraint_type
                                        ,'P',1
                                        ,'U',2
                                        ,'R',3
                                        ,'C',4
                                      )
                      , constraint_name
            ";

  $dbh->{ LongReadLen } = 8192;    # Allows SEARCH_CONDITION length of 8K
  $dbh->{ LongTruncOk } = 1;

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $owner, $name );
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    my ( $cons_name, $cons_type, $condition ) = @$row;

    if ( $cons_type ne 'C' )
    {
      $sql .= _create_constraint( $schema, $owner, $cons_name, $view );
    }
    elsif ( $condition !~ /IS NOT NULL/ )
    {
      $sql .= _create_constraint( $schema, $owner, $cons_name, $view );
    }
  }


  # Add table's triggers
  $stmt =
      "
              SELECT
                            trigger_name
              FROM
                            ${view}_triggers
              WHERE
                                    table_name  = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND table_owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                          trigger_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= _create_trigger( $schema, $owner, @$row->[0], $view );
  }

  return $sql;
}

# sub _create_table_text
#
# Formats the CREATE TABLE statement
#
sub _create_table_text
{
  my ( $stmt, $schema, $owner, $name, $view ) = @_;

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  my @row = $sth->fetchrow_array;

  # Turn warnings off
  $^W = 0;

  my $monitoring   = shift @row;
  my $table        = shift @row;
  my $degree       = shift @row;
  my $instances    = shift @row;
  my $organization = shift @row;

  my (
       # Segment Attributes
       $cache,
       $pct_used,
       $pct_free,
       $ini_trans,
       $max_trans,
       $initial,
       $next,
       $min_extents,
       $max_extents,
       $pct_increase,
       $freelists,
       $freelist_groups,
       $buffer_pool,
       $logging,
       $tablespace,
       $blocks,
     ) = @row;

  ( $initial, $next ) = _initial_next( $blocks ) if $attr{ 'resize' };

  my $sql  =
          "PROMPT " .
          "CREATE TABLE \L$schema$name\n\n" .
          "CREATE TABLE \L$schema$name\n" .
          "(\n    " .
          _table_columns( $owner, $name, $view );

  if ( $organization eq 'INDEX' )
  {
    $stmt =
        "
                  SELECT
                                constraint_name
                  FROM
                                ${view}_constraints
                  WHERE
                                        table_name      = UPPER( ? )
                                AND constraint_type = 'P'
                ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
          "
                                AND owner           = UPPER('$owner')
                    ";
    }

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my ( $index ) = $sth->fetchrow_array;

    $sql .= "  , CONSTRAINT \L$index \UPRIMARY KEY\n" .
            _index_columns( '      ', $owner, $index, $view, );
  }

  $sql .= ")\n";

  $sql .= "ORGANIZATION        $organization\n"    if $oracle_major > 7;

  if (
          $oracle_major > 8
       or ( $oracle_major == 8 and $oracle_minor > 0 )
     )
  {
    $sql .= "$monitoring\n";
  }

  unless (     $organization eq 'INDEX'
           and $oracle_major == 8
           and $oracle_minor == 0
         )
  {
    $sql .= "PARALLEL\n" .
            "(\n" .
            "  DEGREE            $degree\n" .
            "  INSTANCES         $instances\n" .
            ")\n";
  }

  unshift @row, ( '', $organization );

  $sql .= _segment_attributes( \@row );

  if ( $organization eq 'INDEX' )
  {
    if ( $oracle_major == 8 and $oracle_minor == 0 )
    {
      $stmt =
        "
                  SELECT
                                c.column_name
                            , i.pct_threshold
                            , 'HEAP'                        AS organization
                                -- Segment Attributes
                            , 'N/A'                         AS cache
                            , t.pct_used
                            , t.pct_free
                            , DECODE(
                                                t.ini_trans
                                              ,0,1
                                              ,null,1
                                              ,t.ini_trans
                                            )                       AS ini_trans
                            , DECODE(
                                                t.max_trans
                                              ,0,255
                                              ,null,255
                                              ,t.max_trans
                                            )                       AS max_trans
                                -- Storage Clause
                            , t.initial_extent
                            , t.next_extent
                            , t.min_extents
                            , DECODE(
                                                t.max_extents
                                              ,2147483645,'unlimited'
                                              ,           t.max_extents
                                            )                       AS max_extents
                            , NVL(t.pct_increase,0)
                            , NVL(t.freelists,1)
                            , NVL(t.freelist_groups,1)
                            , 'N/A'                         AS buffer_pool
                            , DECODE(
                                                t.logging
                                              ,'NO','NOLOGGING'
                                              ,     'LOGGING'
                                            )                       AS logging
                            , LOWER(t.tablespace_name)      AS tablespace_name
                            , s.blocks - NVL(t.empty_blocks,0)
                  FROM
                                ${view}_tables       t
                            , ${view}_indexes      i
                            , ${view}_segments     s
                            , ${view}_tab_columns  c
                  WHERE
                                        t.iot_name       = UPPER('$name')
                                AND i.table_name     = t.iot_name
                                AND c.table_name (+) = i.table_name
                                AND c.column_id  (+) = i.include_column
                                AND s.segment_name   = t.table_name
                ";
    }
    else                   # We're Oracle8i or newer
    {
      $stmt =
        "
                  SELECT
                                c.column_name
                            , i.pct_threshold
                            , 'HEAP'                        AS organization
                                -- Segment Attributes
                            , 'N/A'                         AS cache
                            , t.pct_used
                            , t.pct_free
                            , DECODE(
                                                t.ini_trans
                                              ,0,1
                                              ,null,1
                                              ,t.ini_trans
                                            )                       AS ini_trans
                            , DECODE(
                                                t.max_trans
                                              ,0,255
                                              ,null,255
                                              ,t.max_trans
                                            )                       AS max_trans
                                -- Storage Clause
                            , t.initial_extent
                            , t.next_extent
                            , t.min_extents
                            , DECODE(
                                                t.max_extents
                                              ,2147483645,'unlimited'
                                              ,           t.max_extents
                                            )                       AS max_extents
                            , NVL(t.pct_increase,0)
                            , NVL(t.freelists,1)
                            , NVL(t.freelist_groups,1)
                            , LOWER(t.buffer_pool)          AS buffer_pool
                            , DECODE(
                                                t.logging
                                              ,'NO','NOLOGGING'
                                              ,     'LOGGING'
                                            )                       AS logging
                            , LOWER(t.tablespace_name)      AS tablespace_name
                            , s.blocks - NVL(t.empty_blocks,0)
                  FROM
                                ${view}_tables       t
                            , ${view}_indexes      i
                            , ${view}_segments     s
                            , ${view}_tab_columns  c
                  WHERE
                                        t.iot_name       = UPPER('$name')
                                AND i.table_name     = t.iot_name
                                AND c.table_name (+) = i.table_name
                                AND c.column_id  (+) = i.include_column
                                AND s.segment_name   = t.table_name
                ";
    }

    if ( $view eq 'DBA' )
    {
      $stmt .=
        "
                                AND t.owner          = UPPER('$owner')
                                AND s.owner          = t.owner
                                AND c.owner      (+) = i.table_owner
                                AND i.table_owner    = t.owner
                ";
    }

    $sth = $dbh->prepare( $stmt );
    $sth->execute;
    my @row = $sth->fetchrow_array;

    if ( @row )
    {
      my $column        = shift @row;
      my $pct_threshold = shift @row;

      $sql .= "PCTTHRESHOLD        $pct_threshold\n";
      $sql .= "INCLUDING           $column\n"    if $column;
      $sql .= "OVERFLOW\n";
    }
  }

  return $sql;
}

# sub _create_tablespace
#
# Returns DDL to create the named tablespace in the form of:
#
#     CREATE [TEMPORARY] TABLESPACE <name>
#     {DATA|TEMP}FILE
#        '<filespec>'
#      , '<filespec>'
#     DEFAULT STORAGE
#     (
#       <storage clause>
#     )
#     [MINIMUM EXTENT  <bytes>]
#     ]PERMANENT|TEMPORARY]
#     [EXTENT MANAGEMENT {DICTIONARY|LOCAL <extent spec>}]
#     [[NO]LOGGING]
#
sub _create_tablespace
{
  my ( $schema, $owner, $name, $view ) = @_;

  die "\nYou must use the DBA views in order to CREATE TABLESPACE\n\n"
      unless $view eq 'DBA';

  my $sql;
  my $stmt;
  my $file_type;

  if ( $oracle_major == 7 )
  {
    $stmt =
      "
              SELECT
                            initial_extent
                        , next_extent
                        , min_extents
                        , DECODE(
                                            max_extents
                                          ,2147483645,'unlimited'
                                          ,null,DECODE(
                                                                      $block_size
                                                                    , 1,  57
                                                                    , 2, 121
                                                                    , 4, 249
                                                                    , 8, 505
                                                                    ,16,1017
                                                                    ,32,2041
                                                                    ,'???'
                                                                  )
                                          ,max_extents
                                        )                       AS max_extents
                        , pct_increase
                        , 0                             AS min_extlen
                        , contents
                        , 'N/A'                         AS logging
                        , 'N/A'                         AS extent_management
                        , 'N/A'                         AS allocation_type
              FROM
                            dba_tablespaces
              WHERE
                            tablespace_name = UPPER( ? )
            ";
  }
  elsif ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            initial_extent
                        , next_extent
                        , min_extents
                        , DECODE(
                                            max_extents
                                          ,2147483645,'unlimited'
                                          ,null,DECODE(
                                                                      $block_size
                                                                    , 1,  57
                                                                    , 2, 121
                                                                    , 4, 249
                                                                    , 8, 505
                                                                    ,16,1017
                                                                    ,32,2041
                                                                    ,'???'
                                                                  )
                                          ,max_extents
                                        )                       AS max_extents
                        , pct_increase
                        , min_extlen
                        , contents
                        , DECODE(
                                            logging 
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , 'N/A'                         AS extent_management
                        , 'N/A'                         AS allocation_type
              FROM
                            dba_tablespaces
              WHERE
                            tablespace_name = UPPER( ? )
            ";
  }
  else             # We're newer than Oracle 8.0
  {
    $stmt =
      "
              SELECT
                            initial_extent
                        , next_extent
                        , min_extents
                        , DECODE(
                                            max_extents
                                          ,2147483645,'unlimited'
                                          ,null,DECODE(
                                                                      $block_size
                                                                    , 1,  57
                                                                    , 2, 121
                                                                    , 4, 249
                                                                    , 8, 505
                                                                    ,16,1017
                                                                    ,32,2041
                                                                    ,'???'
                                                                  )
                                          ,max_extents
                                        )                       AS max_extents
                        , pct_increase
                        , min_extlen
                        , contents
                        , logging
                        , extent_management
                        , allocation_type
              FROM
                            dba_tablespaces
              WHERE
                            tablespace_name = UPPER( ? )
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  die "Tablespace \U$name \Ldoes not exist.\n\n" unless @row;

  my (
       $initial,
       $next,
       $min_extents,
       $max_extents,
       $pct_increase,
       $min_extlen,
       $contents,
       $logging,
       $extent_management,
       $allocation_type,
     ) = @row;

  if ( $extent_management eq 'LOCAL' and $contents eq 'TEMPORARY' )
  {
    $sql  = "PROMPT " .
            "CREATE TEMPORARY TABLESPACE \L$name\n\n" .
            "CREATE TEMPORARY TABLESPACE \L$name\n";
  }
  else
  {
    $sql  = "PROMPT " .
            "CREATE TABLESPACE \L$name\n\n" .
            "CREATE TABLESPACE \L$name\n";
  }

  if ( $oracle_major == 7 )
  {
    $file_type = 'DATA';

    $stmt =
      "
              SELECT
                            file_name
                        , bytes
                        , 'N/A'                                 AS autoextensible
                        , 'N/A'                                 AS maxbytes
                        , 'N/A'                                 AS increment_by
              FROM
                            dba_data_files
              WHERE
                            tablespace_name = UPPER( ? )
              ORDER
                    BY
                            file_name
            ";
  }
  else             # We're newer than Oracle7
  {
    $stmt =
      "
              SELECT
                            count(*)
              FROM
                            dba_data_files
              WHERE
                            tablespace_name = UPPER( ? )
            ";

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my ( $cnt ) = $sth->fetchrow_array;
    $file_type  = ( $cnt == 0 ) ? 'TEMP' : 'DATA';

    $stmt =
      "
              SELECT
                            file_name
                        , bytes
                        , autoextensible
                        , DECODE(
                                            SIGN(2147483645 - maxbytes)
                                          ,-1,'unlimited'
                                          ,maxbytes
                                        )                               AS maxbytes
                        , increment_by * $block_size * 1024     AS increment_by
              FROM
                            dba_${file_type}_files
              WHERE
                            tablespace_name = UPPER( ? )
              ORDER
                    BY
                            file_name
            ";
  }

  $sql .= "${file_type}FILE\n";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;

  my $comma = '  ';
  foreach my $row ( @$aref )
  {
    my (
         $file_name,
         $bytes,
         $autoextensible,
         $maxbytes,
         $increment_by,
       ) = @$row;

    $sql .= "$comma '$file_name' SIZE $bytes REUSE\n";

    if ( $oracle_major > 7 )
    {
      $sql .= "       AUTOEXTEND ";

      if ( $autoextensible eq 'YES' )
      {
        $sql .= "ON NEXT $increment_by MAXSIZE $maxbytes\n";
      }
      else
      {
        $sql .= "OFF\n";
      }
    }

    $comma = ' ,';
  }

  if ( $extent_management eq 'LOCAL' )
  {
    $sql .= "EXTENT MANAGEMENT LOCAL ";

    if ( $allocation_type eq 'SYSTEM' )
    {
      $sql .= "AUTOALLOCATE\n";
    }
    else
    {
      $sql .= "UNIFORM SIZE $next\n";
    }
  }
  else  # It's Dictionary Managed, Oracle8.0 or Oracle7
  {
    $sql .= "DEFAULT STORAGE\n" .
            "(\n" .
            "  INITIAL           $initial\n" .
            "  NEXT              $next\n" .
            "  MINEXTENTS        $min_extents\n" .
            "  MAXEXTENTS        $max_extents\n" .
            "  PCTINCREASE       $pct_increase\n" .
            ")\n" .
            "$contents\n";;

    if ( $min_extlen > 0 )
    {
      $sql .= "MINUMUM EXTENT      $min_extlen\n";
    }

    if (
            $oracle_major > 8
         or ( $oracle_major == 8 and $oracle_minor > 0 )
       )
    {
      $sql .= "EXTENT MANAGEMENT DICTIONARY\n";
    }
  }

  if ( $oracle_major > 7 )
  {
    $sql .= "$logging\n"    unless (
                                         $contents          eq 'TEMPORARY'
                                     and $extent_management eq 'LOCAL'
                                   );
  }

  $sql .= ";\n\n";

  return $sql;
}

# sub _create_trigger
#
# Returns DDL to create the named trigger in the form of:
#
#     CREATE OR REPLACE TRIGGER [schema.]<name>
#     {BEFORE|AFTER|INSTEAD OF} <triggering event>
#     [OF <column list ]ON {[schema.]<table>|DATABASE|SCHEMA}
#     REFERENCING <new> AS NEW <old> AS OLD
#     [WHEN <whatever>]
#     [FOR EACH ROW]
#     <code>
# 
sub _create_trigger
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $stmt;
  if (
          $oracle_major > 8
       or ( $oracle_major == 8 and $oracle_minor > 0 )
     )
  {
    $stmt =
      "
              SELECT
                            trigger_type
                        , RTRIM(triggering_event)
                        , table_owner
                        , table_name
                        , base_object_type
                        , referencing_names
                        , DECODE(
                                            when_clause
                                          ,null,null
                                          ,'WHEN (' || when_clause || ')' || CHR(10)
                                        )
                        , trigger_body
              FROM
                            ${view}_triggers
              WHERE
                                    trigger_name = UPPER( ? )
            ";
  }
  else
  {
    $stmt =
      "
              SELECT
                            trigger_type
                        , RTRIM(triggering_event)
                        , table_owner
                        , table_name
                            -- Only table triggers before 8i
                        , 'TABLE'                           AS base_object_type
                        , referencing_names
                        , DECODE(
                                            when_clause
                                          ,null,null
                                          ,'WHEN (' || when_clause || ')' || CHR(10)
                                        )
                        , trigger_body
              FROM
                            ${view}_triggers
              WHERE
                                    trigger_name = UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND table_owner  = UPPER('$owner')
            ";
  }

  $dbh->{ LongReadLen } = 65536;    # Allows TRIGGER_BODY length of 64K
  $dbh->{ LongTruncOk } = 1;

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  die "\nTrigger \U$schema$name \Ldoes not exist.\n\n" unless @row;

  my (
       $trigger_type,
       $event,
       $table_owner,
       $table,
       $base_type,
       $ref_names,
       $when,
       $body,
     ) = @row;

  my ( $trg_type ) = $trigger_type =~ /(BEFORE|AFTER|INSTEAD OF)/;
  my $schema2 = _set_schema( $table_owner );
  my $object = ( $base_type eq 'TABLE'  ) ? $schema2 . $table   :
               ( $base_type eq 'SCHEMA' ) ? $schema  . 'SCHEMA' : $base_type;
  $ref_names =~ s/ING (\w+) AS NEW (\w+)/ING \L$1 \UAS NEW \L$2/;

  # When clause sometimes ends in a null
  $when =~ s/\c@//g;

  # Body sometimes ends in a null
  $body =~ s/\c@//g;

  my $columns;
  if ( $event =~ /UPDATE/ )
  {
    $stmt =
      "
              SELECT
                            LOWER(column_name)
              FROM
                            ${view}_trigger_cols
              WHERE
                                    trigger_name = UPPER( ? )
                            AND column_list  = 'YES'
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                            AND table_owner  = UPPER('$owner')
            ";
    }

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );

    my (@columns) = map { $_->[0] } @{ $sth->fetchall_arrayref };

    $columns = "\nOF\n    " . join ("\n  , ", @columns) . "\n" if ( @columns );
  }


  my $sql = "PROMPT " .
            "CREATE OR REPLACE TRIGGER \L$schema$name\n\n" .
            "CREATE OR REPLACE TRIGGER \L$schema$name\n" .
            "$trg_type $event ${columns}ON \L$object\n";

  $sql .= "$ref_names\n"      if $base_type =~ /TABLE|VIEW/;
  $sql .= "FOR EACH ROW\n"    if $trigger_type =~ /EACH ROW/;
  $sql .= $when               if $when;

  $sql .= $body;
  $sql .= "\n"    unless $sql =~ /\Z\n/;
  $sql .= "/\n\n";

  return $sql;
}

# sub _create_type
#
# Returns DDL to create the named procedure in the form of:
#
#     CREATE OR REPLACE TYPE [schema.]<name>
#     AS
#     <source>
# 
# by calling _display_source
#
sub _create_type
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql = _display_source( $schema, $owner, $name, $view, 'TYPE' );

  if ( $attr{ 'grants' } )
  {
    $sql .= _object_privs( $schema, $owner, $name, $view );
  }

  return $sql;
}

# sub _create_user
#
# Returns DDL to create the named user in the form of:
#
#     CREATE USER <name> IDENTIFIED {EXTERNALLY|BY VALUES '<values>'}
#        PROFILE               <profile>
#        DEFAULT TABLESPACE    <tablespace>
#        TEMPORARY TABLESPACE  <tablespace>
#        [QUOTA {UNLIMITED|<bytes>} ON TABLESPACE <tablespace1>]
#        [QUOTA {UNLIMITED|<bytes>} ON TABLESPACE <tablespace2>]
#
#     [GRANT <role >            TO <name> [WITH ADMIN OPTION]]
#     [GRANT <system privilege> TO <name> [WITH ADMIN OPTION]]
#     [GRANT <privilege> ON <object> TO <name> [WITH GRANT OPTION]]
# 
sub _create_user
{
  my ( $schema, $owner, $name, $view ) = @_;

  die "\nYou must use the DBA views in order to CREATE USER\n\n"
      unless $view eq 'DBA';

  my $stmt =
      "
              SELECT
                            DECODE(
                                            password
                                          ,'EXTERNAL','EXTERNALLY'
                                          ,'BY VALUES ''' || password || ''''
                                        )                         AS password
                        , profile
                        , default_tablespace
                        , temporary_tablespace
              FROM
                            dba_users
              WHERE
                            username = UPPER( ? )
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my @row = $sth->fetchrow_array;
  die "\nUser \U$name \Ldoes not exist.\n\n" unless @row;

  my (
       $password,
       $profile,
       $default_tablespace,
       $temporary_tablespace,
     ) = @row;

  my $sql  =
          "PROMPT " .
          "CREATE USER \L$name\n\n" .
          "CREATE USER \L$name \UIDENTIFIED $password\n" .
          "   \UPROFILE              \L$profile\n" .
          "   \UDEFAULT TABLESPACE   \L$default_tablespace\n" .
          "   \UTEMPORARY TABLESPACE \L$temporary_tablespace\n";

  # Add tablespace quotas
  $stmt =
      "
              SELECT
                            DECODE(
                                            max_bytes
                                          ,-1,'unlimited'
                                          ,TO_CHAR(max_bytes,'99999999')
                                        )                         AS max_bytes
                        , tablespace_name
              FROM
                            dba_ts_quotas
              WHERE
                            username = UPPER( ? )
              ORDER
                    BY
                            tablespace_name
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= "   QUOTA  @$row->[0]  ON \L@$row->[1]\n";
  }

  $sql .= ";\n\n";

  $sql .= _granted_privs( $name );

  return $sql;
}

# sub _create_view
#
# Returns DDL to create the named view in the form of:
#
#     CREATE OR REPLACE VIEW [schema.]<name>
#     (
#       <column aliases>
#     )
#     AS
#     <query>
# 
sub _create_view
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $stmt =
      "
              SELECT
                            text
              FROM
                            ${view}_views
              WHERE
                                    view_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner     = UPPER('$owner')
            ";
  }

  $dbh->{ LongReadLen } = 65536;    # Allows View TEXT length of 64K
  $dbh->{ LongTruncOk } = 1;

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my ( $text ) = $sth->fetchrow_array;
  die "\nView \U$name \Ldoes not exist.\n\n" unless $text;

  $stmt =
      "
              SELECT
                            LOWER(column_name)
              FROM
                            ${view}_tab_columns
              WHERE
                                    table_name = UPPER( ? )
            ";
 
  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner      = UPPER('$owner')
            ";
  }
 
  $stmt .= 
      "
              ORDER
                    BY
                            column_id
            ";
 
  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my (@columns) = map { $_->[0] } @{ $sth->fetchall_arrayref };

  return "PROMPT " .
         "CREATE OR REPLACE VIEW \L$schema$name\n\n" .
         "CREATE OR REPLACE VIEW \L$schema$name\n" .
         "(\n    " .
         join ("\n  , ", @columns) .
         "\n)\n" .
         "AS\n" .
         "$text" .
         ";\n\n";
}

# sub _display_source
#
# Returns DDL to create the named stored item in the form of:
#
#     CREATE OR REPLACE <TYPE> [schema.]<name>
#     <source>
#
# where TYPE is one of:  PROCEDURE, FUNCTION, PACKAGE, PACKAGE BODY
# 
sub _display_source
{
  my ( $schema, $owner, $name, $view, $type ) = @_;

  my $sql;
  my $stmt =
      "
              SELECT
                            text
              FROM
                            ${view}_source
              WHERE
                                    type  = '$type'
                            AND name  = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                          line
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;
  die "\n\u\L$type \U$name \Ldoes not exist.\n\n" unless @$aref;

  $sql = "PROMPT " .
         "CREATE OR REPLACE $type \L$schema$name\n\n" .
         "CREATE OR REPLACE ";

  my $i = 1;
  foreach my $row ( @$aref )
  {
    if ( $i++ == 1 )
    {
      # source.text already includes <TYPE> <name> 
      # We want to insert the schema right before the name

#      @$row->[0] =~ s/$type\s+\S+/$type \L$schema$name/i;
#     The following line was submitted by Sandor Toth, to fix the condition
#     where parameters immediately followed the name, as in
#        CREATE OR REPLACE PROCEDURE foo( var1 IN VARCHAR2)
#                                       ^
#                                       |
#                                observe, no space between 'foo' and '('

      @$row->[0] =~ s/($type)\s+($name)\s*(.*)$/$1 \L$schema$2 $3/i;
    }

    $sql .= "@$row->[0]";
  }

  $sql .= "\n"    unless $sql =~ /\Z\n/;
  $sql .= "/\n\n";

  return $sql;
}

# sub _drop_constraint
#
# Returns DDL to drop the named constraint in the form of:
#
#     ALTER TABLE [schema.]<name> DROP CONSTRAINT <name>
#
sub _drop_constraint
{
  my ( $schema, $name, $type, $owner,  $view ) = @_;

  my $stmt =
      "
              SELECT
                            table_name
              FROM
                            ${view}_constraints
              WHERE
                                    owner           = UPPER( ? )
                            AND constraint_name = UPPER( ? )
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $owner, $name );
  my ( $table_name ) = $sth->fetchrow_array;

  return "PROMPT " .
         "ALTER TABLE \L$schema$table_name \UDROP CONSTRAINT \L$name \n\n" .
         "ALTER TABLE \L$schema$table_name \UDROP CONSTRAINT \L$name;\n\n";
}

# sub _drop_database_link
#
# Returns DDL to drop the named database link in the form of:
#
#     DROP [PUBLIC] DATABASE LINK <name>
#
sub _drop_database_link
{
  my ( $schema, $name, $type ) = @_;

  my $public = ( $schema eq 'PUBLIC' ) ? ' PUBLIC' : '';

  return "PROMPT " .
         "DROP$public DATABASE LINK \L$name  \n\n" .
         "DROP$public DATABASE LINK \L$name ;\n\n" ;
}

# sub _drop_materialized_view_log
#
# Returns DDL to drop the named materialized view
# by calling _drop_mview_log (which is shared with
# _drop_snapshot_log)
#
sub _drop_materialized_view_log
{
  my ( $schema, $name, $type ) = @_;

  _drop_mview_log( $schema, $name, 'MATERIALIZED VIEW' );
}

# sub _drop_mview_log
#
# Returns DDL to drop the named database link in the form of:
#
#     DROP MATERIALIZED VIEW LOG ON [schema.]<name>
#     or
#     DROP SNAPSHOT LOG ON [schema.]<name>
#
sub _drop_mview_log
{
  my ( $schema, $name, $type ) = @_;

  return "PROMPT " .
         "DROP $type LOG ON \L$schema$name  \n\n" .
         "DROP $type LOG ON \L$schema$name ;\n\n" ;
}

# sub _drop_object
# 
# Returns generic DDL to drop the named object in the form of:
# 
#     DROP <TYPE> <name>
#
sub _drop_object
{
  my ( $schema, $name, $type ) = @_;

  return "PROMPT " .
         "DROP \U$type \L$name  \n\n" .
         "DROP \U$type \L$name ;\n\n";
}

# sub _drop_profile
#
# Returns DDL to drop the named profile in the form of:
#
#     DROP PROFILE <name> CASCADE
#
sub _drop_profile
{
  my ( $schema, $name, $type ) = @_;

  my $public = ( $schema eq 'PUBLIC' ) ? ' PUBLIC' : '';

  return "PROMPT " .
         "DROP PROFILE \L$name \UCASCADE   \n\n" .
         "DROP PROFILE \L$name \UCASCADE ; \n\n" ;
}

# sub _drop_schema_object
# 
# Returns generic DDL to drop the named object in the form of:
# 
#     DROP <TYPE> [schema.]<name>
#
sub _drop_schema_object
{
  my ( $schema, $name, $type ) = @_;

  return "PROMPT " .
         "DROP \U$type \L$schema$name  \n\n" .
         "DROP \U$type \L$schema$name ;\n\n";
}

# sub _drop_snapshot_log
#
# Returns DDL to drop the named snapshot log
# by calling _drop_mview_log (which is shared with
# _drop_materialized_view_log)
#
sub _drop_snapshot_log
{
  my ( $schema, $name, $type ) = @_;

  _drop_mview_log( $schema, $name, 'SNAPSHOT' );
}

# sub _drop_synonym
#
# Returns DDL to drop the named object in the form of:
#
#     DROP PUBLIC SYNONYM  <name>
#      or
#     DROP SYNONYM [schema.]<name> 
#
sub _drop_synonym
{
  my ( $schema, $name, $type ) = @_;

  my $public = ( $schema eq 'PUBLIC' ) ? ' PUBLIC' : '';

  if ( $public )
  {
      return "PROMPT " .
             "DROP PUBLIC SYNONYM \L$name  \n\n" .
             "DROP PUBLIC SYNONYM \L$name ;\n\n" ; 
  } else
  {
      return "PROMPT " .
             "DROP SYNONYM  \L$schema$name  \n\n" .
             "DROP SYNONYM  \L$schema$name ;\n\n";
  }
}

# sub _drop_table
# 
# Returns DDL to drop the named table in the form of:
# 
#     DROP TABLE [schema.]<name> CASCADE CONSTRAINTS
#
sub _drop_table
{
  my ( $schema, $name, $type ) = @_;

  return "PROMPT " .
         "DROP TABLE \L$schema$name \UCASCADE CONSTRAINTS  \n\n" .
         "DROP TABLE \L$schema$name \UCASCADE CONSTRAINTS ;\n\n";
}

# sub _drop_tablespace
# 
# Returns DDL to drop the named tablespace in the form of:
# 
#     DROP TABLESPACE <name> INCLUDING CONTENTS CASCADE CONSTRAINTS
#
sub _drop_tablespace
{
  my ( $schema, $name, $type ) = @_;

  return "PROMPT " .
         "DROP TABLESPACE \L$name " .
              "\UINCLUDING CONTENTS CASCADE CONSTRAINTS  \n\n" .
         "DROP TABLESPACE \L$name " .
              "\UINCLUDING CONTENTS CASCADE CONSTRAINTS ;\n\n";
}

# sub _drop_user
# 
# Returns DDL to drop the named user in the form of:
# 
#     DROP USER <name> CASCADE
#
sub _drop_user
{
  my ( $schema, $name, $type ) = @_;

  return "PROMPT " .
         "DROP USER \L$name \UCASCADE  \n\n" .
         "DROP USER \L$name \UCASCADE ;\n\n";
}

#sub _generate_heading
#
# Initializes $ddl
#
sub _generate_heading
{
  $ddl = "";
  return unless $attr{ heading };

  my ( $module, $action, $type, $list ) = @_;

  $ddl =  "REM This DDL was reverse engineered by\n" .
          "REM Perl module $module, Version $DDL::Oracle::VERSION\n" .
          "REM\n" .
          "REM at:   $host\n" .
          "REM from: $instance, an Oracle Release $oracle_release instance\n" .
          "REM\n" .
          "REM on:   " . scalar ( localtime ) . "\n" .
          "REM\n";

  if ( $action eq 'FREE SPACE' )
  {
    $ddl .= "REM Generating $action \Lreport";
  }
  elsif ( "\U$type" eq 'COMPONENTS' )
  {
    $ddl .= "REM Generating CREATE <component> statements\n\n";
    return;
  }
  elsif ( "\U$type" eq 'SCHEMA' )
  {
    $ddl .= "REM Generating CREATE statements for all Objects in schema";
  }
  else
  {
    $ddl .= "REM Generating $action \U$type \Lstatement";
  }

  $ddl .= ( @$list == 1 ) ? '' : "s";

  if ( "\U$type" eq 'SCHEMA' )
  {
    $ddl .= "\n";
  }
  else
  {
    $ddl .= " for:\n";
  }

  $ddl .= "REM\n"; 

  # Only include the schema if the Type has such a beast.
  foreach my $row ( @$list )
  {
    # These don't
    if (
            "\L$type" eq 'directory'
         or "\L$type" eq 'library'
         or "\L$type" eq 'profile'
         or "\L$type" eq 'role'
         or "\L$type" eq 'rollback segment'
         or "\L$type" eq 'tablespace'
         or "\L$type" eq 'user'
         or (
                  "\L@$row->[0]" ne 'public'
              and (
                       "\L$type" eq 'database link'
                    or "\L$type" eq 'synonym'
                  )
            )
       )
    {
      $ddl .= "REM\t\U@$row->[1]\n";
    }
    # Schema's don't name individual objects
    elsif ( "\L$type" eq 'schema' )
    {
      $ddl .= "REM\t\U@$row->[0]\n";
    }
    # The rest do.
    else
    {
      $ddl .= "REM\t\U@$row->[0].@$row->[1]\n";
    }
  }

  return $ddl .= "\n";
};

# sub _get_oracle_release
#
# Determines Oracle Release number
#
sub _get_oracle_release
{
  $sth = $dbh->prepare(
      "
              SELECT
                            version
              FROM
                            product_component_version
              WHERE
                            product LIKE 'Oracle%'
            ");

  $sth->execute;
  my $version = $sth->fetchrow_array;

  (
    $oracle_release,
    $oracle_major,
    $oracle_minor
  ) = $version =~ /((\d+)\.(\d+)\S+)/;

  if ( $attr{ heading } )
  {
    $sth = $dbh->prepare(
      "
              SELECT
                            LOWER(name)
              FROM
                            v\$database
            ");

    $sth->execute;
    ( $instance ) = $sth->fetchrow_array;

    $host = `hostname`;
    chomp( $host );
  }
}

# sub _granted_privs
#
# Returns DDL to create GRANT statements to the named grantee 
# in the form of:
#
#     [GRANT <role >            TO <name> {WITH ADMIN OPTION]]
#     [GRANT <system privilege> TO <name> {WITH ADMIN OPTION]]
#
sub _granted_privs
{
  my ( $name ) = @_;

  my $sql;

  # Add role privileges
  my $stmt =
      "
              SELECT
                            granted_role
                        , DECODE(
                                            admin_option
                                          ,'YES','WITH ADMIN OPTION '
                                          ,null
                                        )                         AS admin_option
              FROM
                            dba_role_privs
              WHERE
                            grantee = UPPER( ? )
              ORDER
                    BY
                            granted_role
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= "PROMPT " .
            "GRANT \L@$row->[0] \UTO \L$name \U@$row->[1] \n\n" .
            "GRANT \L@$row->[0] \UTO \L$name \U@$row->[1];\n\n";
  }

  # Add system privileges
  $stmt =
      "
              SELECT
                            privilege
                        , DECODE(
                                            admin_option
                                          ,'YES','WITH ADMIN OPTION '
                                          ,null
                                        )                         AS admin_option
              FROM
                            dba_sys_privs
              WHERE
                            grantee = UPPER( ? )
              ORDER
                    BY
                            privilege
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    $sql .= "PROMPT " .
            "GRANT \L@$row->[0] \UTO \L$name \U@$row->[1] \n\n" .
            "GRANT \L@$row->[0] \UTO \L$name \U@$row->[1];\n\n";
  }

  return $sql;
}

#sub _index_columns
#
# Returns a formatted string containing the index columns.
# Starting with Oracle8i, columns may be DESCending.
#
sub _index_columns
{
  my ( $indent, $owner, $name, $view, ) = @_;

  my $stmt;
  if (
          $oracle_major > 8
       or ( $oracle_major == 8 and $oracle_minor > 0 )
     )
  {
    $stmt =
      "
              SELECT
                            LOWER(column_name)
                        , descend
              FROM
                            ${view}_ind_columns
              WHERE
                                    index_name  = UPPER( ? )
            ";
  }
  else
  {
    $stmt =
      "
              SELECT
                            LOWER(column_name)
                        , 'ASC'
              FROM
                            ${view}_ind_columns
              WHERE
                                    index_name  = UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND index_owner = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                          column_position
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;

  my @cols;
  foreach my $row ( @$aref )
  {
    my ( $column, $direction ) = @$row;

    if ( $column =~ /^sys_nc\d+\$$/ )
    {
      $stmt =
        "
                  SELECT
                                c.default\$
                  FROM
                                sys.col\$        c           -- User needs SELECT on this
                            , ${view}_indexes  i
                            , ${view}_objects  o
                  WHERE
                                        i.index_name  = UPPER( ? )
                                AND o.object_name = i.table_name
                                AND c.obj#        = o.object_id
                                AND c.name        = UPPER( ? )
                ";

      if ( $view eq 'DBA' )
      {
        $stmt .=
        "
                                AND i.owner       = UPPER('$owner')
                                AND o.owner       = i.table_owner
                ";
      }

      $dbh->{ LongReadLen } = 1024;
      $dbh->{ LongTruncOk } = 1;

      $sth = $dbh->prepare( $stmt );
      $sth->execute( $name, $column );
      my ( $real_column ) = $sth->fetchrow_array;
      # Get rid of double quotes
      $real_column =~ s|\"||g;

      if ( $direction eq 'DESC' )
      {
        push @cols, "\L$real_column" . 
                    '  ' . 
                    ' ' x ( 30 - length( $real_column ) ) .
                    "DESC";
      }
      else   # Must be a function-based index
      {
        push @cols, $real_column;
      }
    }
    else
    {
      push @cols, $column;
    }
  }

  return "${indent}(\n$indent    " .
         join ( "\n$indent  , ", @cols ) .
         "\n${indent})\n";
}

# sub _initial_next
#
# Given the number of blocks in a object, returns the smallest
# INITIAL/NEXT values appropriate for an object of this size.
#
sub _initial_next
{
  my $blocks  = shift;

  # Turn warnings off
  $^W = 0;

  my $i = 0;
  my $initial;
  my $next;

  until ( $initial ) 
  {
    $initial = ( $size_arr[$i][0] eq "UNLIMITED" ) ? $size_arr[$i][1] :
               ( $size_arr[$i][0]  > $blocks     ) ? $size_arr[$i][1] :
                                                     undef;

    $next    = ( $size_arr[$i][0] eq "UNLIMITED" ) ? $size_arr[$i][1] :
               ( $size_arr[$i][0]  > $blocks     ) ? $size_arr[$i][1] :
                                                     undef;
    $i++;
  }
  return $initial, $next;
}

#sub _key_columns
#
# Returns a formatted string containing the partitioning key columns.
# Called from _partition_key_columns and _subpartition_key_columns,
# which merely control which key columns table to query.
#
sub _key_columns
{
  my ( $owner, $name, $object_type, $view, $table ) = @_;

  my $stmt =
      "
              SELECT
                            LOWER(column_name)
              FROM
                            ${view}_${table}_key_columns
              WHERE
                                    name           = UPPER( ? )
                            AND object_type LIKE UPPER('$object_type%')
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner          = UPPER('$owner')
            ";
  }

  $stmt .= 
      "
              ORDER
                    BY
                          column_position
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;

  my @cols;
  foreach my $row ( @$aref )
  {
    push @cols, $row->[0];
  }

  return join ( "\n  , ", @cols );
}

# sub _object_privs
#
# Returns DDL to create GRANT statements in the form of:
#
#     GRANT <privilege> ON <object> TO <name> [WITH GRANT OPTION]
#
#       and/or
#
#     GRANT <privilege>
#     (
#         column_name
#       , column_name
#     )
#     ON <object> TO <name> [WITH GRANT OPTION]
#
sub _object_privs
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $sql;

  my $stmt =
      "
              SELECT
                            grantee
                        , privilege
                        , DECODE(
                                            grantable
                                          ,'YES','WITH GRANT OPTION '
                                          ,null
                                        )                         AS grantable
              FROM
                            ${view}_tab_privs
              WHERE
                                    grantor    = UPPER( ? )
                            AND table_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner      = UPPER('$owner')
            ";
  }

  $stmt .=
      "
              ORDER
                    BY
                            grantee
                        , privilege
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $owner, $name );
  my $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    my (
         $grantee,
         $privilege,
         $grantable,,
       ) = @$row;

    $sql .= "PROMPT " .
            "GRANT \L$privilege \UON \L$schema$name \UTO \L$grantee " .
            "\U$grantable \n\n" .
            "GRANT \L$privilege \UON \L$schema$name \UTO \L$grantee " .
            "\U$grantable;\n\n";
  }

  $stmt =
      "
              SELECT DISTINCT
                            grantee
                        , privilege
                        , DECODE(
                                            grantable
                                          ,'YES','WITH GRANT OPTION '
                                          ,null
                                        )                         AS grantable
              FROM
                            ${view}_col_privs
              WHERE
                                    grantor    = UPPER( ? )
                            AND table_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner      = UPPER('$owner')
            ";
  }

  $stmt .=
      "
              ORDER
                    BY
                            grantee
                        , privilege
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $owner, $name );
  $aref = $sth->fetchall_arrayref;

  foreach my $row ( @$aref )
  {
    my (
         $grantee,
         $privilege,
         $grantable,,
       ) = @$row;

    $sql .= "PROMPT " .
            "GRANT \L$privilege \UON \L$schema$name \UTO \L$grantee " .
            "\U$grantable\n\n" .
            "GRANT \L$privilege \n(\n    ";

    $stmt =
      "
              SELECT
                            LOWER(column_name)
              FROM
                            ${view}_col_privs
              WHERE
                                    grantor    = UPPER( ? )
                            AND privilege  = UPPER( ? )
                            AND table_name = UPPER( ? )
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                            AND owner      = UPPER('$owner')
            ";
    }

    $stmt .=
      "
              ORDER
                    BY
                            grantee
                        , privilege
            ";

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $owner, $privilege, $name );
    my $aref = $sth->fetchall_arrayref;

    my @cols;
    foreach my $row ( @$aref )
    {
      push @cols, $row->[0];
    }

    $sql .= join ( "\n  , ", @cols );

    $sql .= "\n)\n\UON \L$schema$name \UTO \L$grantee \U$grantable;\n\n";
  }

  return $sql;
}

# sub _partition_key_columns
#
# Returns a formatted string containing the partitioning key columns
#
sub _partition_key_columns
{
  return _key_columns( @_, 'PART' );
}

# sub _print_space
#
# Returns a formatted string containing the space analysis report.
#
sub _print_space
{
  my ( $owner, $name, $type, $partition ) = @_;

  my (
       $total_blocks,
       $total_bytes,
       $unused_blocks,
       $unused_bytes,
       $last_file_id,
       $last_block_id,
       $last_block,
       $free_blocks,
     );

  my $max_length = 20;
  my $freelist_group_id = 0;

  if ( $partition )
  {
    $sth = $dbh->prepare(
                          "
                                                      BEGIN 
                                                          dbms_space.unused_space (
                                                                                                              ?,?,?,?,?,?,?,?,?,?,?
                                                                                                          ) ;
                                                      END ;
                                                    "
                        );
    $sth->bind_param( 1, "\U$owner" );
    $sth->bind_param( 2, "\U$name"  );
    $sth->bind_param( 3, "\U$type"  );
    $sth->bind_param_inout( 4 , \$total_blocks , $max_length );
    $sth->bind_param_inout( 5 , \$total_bytes  , $max_length );
    $sth->bind_param_inout( 6 , \$unused_blocks, $max_length );
    $sth->bind_param_inout( 7 , \$unused_bytes , $max_length );
    $sth->bind_param_inout( 8 , \$last_file_id , $max_length );
    $sth->bind_param_inout( 9 , \$last_block_id, $max_length );
    $sth->bind_param_inout( 10, \$last_block   , $max_length );
    $sth->bind_param( 11, $partition );
    $sth->execute;

    $sth = $dbh->prepare(
                          "
                                                      BEGIN 
                                                          dbms_space.free_blocks(
                                                                                                            ?,?,?,?,?,?,?
                                                                                                        ) ;
                                                      END ;
                                                    "
                        );
    $sth->bind_param( 1, "\U$owner"          );
    $sth->bind_param( 2, "\U$name"           );
    $sth->bind_param( 3, "\U$type"           );
    $sth->bind_param( 4, $freelist_group_id  );
    $sth->bind_param_inout( 5 , \$free_blocks, $max_length );
    $sth->bind_param( 6, '' );
    $sth->bind_param( 7, $partition );
    $sth->execute;
  }
  else
  {
    $sth = $dbh->prepare(
                          "
                                                      BEGIN 
                                                          dbms_space.unused_space (
                                                                                                              ?,?,?,?,?,?,?,?,?,?
                                                                                                          ) ;
                                                      END ;
                                                    "
                        );
    $sth->bind_param( 1, "\U$owner" );
    $sth->bind_param( 2, "\U$name"  );
    $sth->bind_param( 3, "\U$type"  );
    $sth->bind_param_inout( 4 , \$total_blocks , $max_length );
    $sth->bind_param_inout( 5 , \$total_bytes  , $max_length );
    $sth->bind_param_inout( 6 , \$unused_blocks, $max_length );
    $sth->bind_param_inout( 7 , \$unused_bytes , $max_length );
    $sth->bind_param_inout( 8 , \$last_file_id , $max_length );
    $sth->bind_param_inout( 9 , \$last_block_id, $max_length );
    $sth->bind_param_inout( 10, \$last_block   , $max_length );
    $sth->execute;

    $sth = $dbh->prepare(
                          "
                                                      BEGIN 
                                                          dbms_space.free_blocks(
                                                                                                            ?,?,?,?,?
                                                                                                        ) ;
                                                      END ;
                                                    "
                        );
    $sth->bind_param( 1, "\U$owner"          );
    $sth->bind_param( 2, "\U$name"           );
    $sth->bind_param( 3, "\U$type"           );
    $sth->bind_param( 4, $freelist_group_id  );
    $sth->bind_param_inout( 5 , \$free_blocks, $max_length );
    $sth->execute;
  }

  my $text;

  if ( $partition )
  {
    $text = "Partition $partition         BYTES";
  }
  else
  {
    $text = "                                                 BYTES";
  }

  $text .= "     BLOCKS\n" .
           "                                          ============" .
           "  =========\n" .
           sprintf(
                    "Used BELOW the high water mark            %12d  %9d\n",
                    $total_bytes - $unused_bytes 
                      - $free_blocks * $block_size * 1024,
                    $total_blocks - $unused_blocks - $free_blocks
                  ) .
           sprintf(
                    "Free ABOVE the high water mark            %12d  %9d\n",
                    $unused_bytes, $unused_blocks
                  ) .
           sprintf(
                    "Free BELOW the high water mark            %12d  %9d\n",
                    $free_blocks * $block_size * 1024, $free_blocks
                  ) .
           "                                          ------------" .
           "  ---------\n" .
           sprintf(
                    "              TOTAL in segment            %12d  %9d\n\n",
                    $total_bytes, $total_blocks
                  ) .
           "                                     FILE_ID  BLOCK_ID" .
           "  BLOCK_NBR\n" .
           "                                     =======  ========" .
           "  =========\n" .
           sprintf(
                    "Last extent having data              %7d  %8d  %9d\n\n",
                    $last_file_id, $last_block_id, $last_block
                  );

  return (
           $text,
           $total_blocks,
           $total_bytes,
           $unused_blocks,
           $unused_bytes,
           $free_blocks,
         );
}

# sub _range_partitions
#
# Returns the ordered list of index range partitions with segment attributes
#
sub _range_partitions
{
  my ( $owner, $index, $view, $subpartitioning_type, $caller ) = @_;

  my $sql .= "(\n";
  my $stmt;

  if ( $oracle_major == 8 and $oracle_minor == 0 )
  {
    $stmt =
      "
              SELECT
                            partition_name
                        , high_value
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , pct_free
                        , ini_trans
                        , max_trans
                            -- Storage Clause
                        , initial_extent
                        , next_extent
                        , min_extent
                        , DECODE(
                                            max_extent
                                          ,2147483645,'unlimited'
                                          ,           max_extent
                                        )                       AS max_extents
                        , pct_increase
                        , NVL(freelists,1)
                        , NVL(freelist_groups,1)
                        , 'N/A'                         AS buffer_pool
                        , DECODE(
                                            logging 
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , tablespace_name
                        , leaf_blocks                   AS blocks
              FROM
                            ${view}_ind_partitions
              WHERE
                                    index_name =  UPPER( ? )
            ";
  }
  else               # We're Oracle8i or newer
  {
    $stmt =
      "
              SELECT
                            partition_name
                        , high_value
                        , 'N/A'                         AS cache
                        , 'N/A'                         AS pct_used
                        , pct_free
                        , ini_trans
                        , max_trans
                            -- Storage Clause
                        , initial_extent
                        , next_extent
                        , min_extent
                        , DECODE(
                                            max_extent
                                          ,2147483645,'unlimited'
                                          ,           max_extent
                                        )                       AS max_extents
                        , pct_increase
                        , NVL(freelists,1)
                        , NVL(freelist_groups,1)
                        , LOWER(buffer_pool)
                        , DECODE(
                                            logging 
                                          ,'NO','NOLOGGING'
                                          ,     'LOGGING'
                                        )                       AS logging
                        , tablespace_name
                        , leaf_blocks                   AS blocks
              FROM
                            ${view}_ind_partitions
              WHERE
                                    index_name =  UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
    "
                        AND index_owner = UPPER('$owner')
        ";
  }

  $stmt .=
      "
              ORDER
                    BY
                            partition_name
            ";

  $dbh->{ LongReadLen } = 8192;    # Allows HIGH_VALUE length of 8K
  $dbh->{ LongTruncOk } = 1;

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $index );
  my $aref = $sth->fetchall_arrayref;

  my $comma = '    ';

  foreach my $row ( @$aref )
  {
    my $partition  = shift @$row;
    my $high_value = shift @$row;

    $sql .= "${comma}PARTITION \L$partition";

    if ($caller ne 'LOCAL' )
    {
      $sql .= " VALUES LESS THAN\n" .
              "      (\n" .
              "        $high_value\n" .
              "      )\n";
    }
    else
    {
      $sql .= "\n";
    }

    unshift @$row, ( '      ', 'INDEX' );

    $sql .= _segment_attributes( $row );

    if ( $subpartitioning_type eq 'HASH' )
    {
      $stmt =
        "
                  SELECT
                                subpartition_name
                            , tablespace_name
                  FROM
                                ${view}_ind_subpartitions
                  WHERE
                                        index_name     =  UPPER( ? )
                                AND partition_name = '$partition'
                ";

      if ( $view eq 'DBA' )
      {
        $stmt .=
        "
                                AND index_owner    = UPPER('$owner')
                ";
      }

      $stmt .=
        "
                  ORDER
                        BY
                                subpartition_name
                ";

      $sth = $dbh->prepare( $stmt );
      $sth->execute( $index );
      my $aref = $sth->fetchall_arrayref;

      $sql .= "        (\n            ";

      my @cols;
      foreach my $row ( @$aref )
      {
        push @cols, "SUBPARTITION \L$row->[0] \UTABLESPACE \L$row->[1]";
      }
      $sql .= join ( "\n          , ", @cols );

      $sql .= "\n        )\n";
    }

    $comma = '  , ';
  }
  $sql .= ");\n\n";

  return $sql;
}

# sub _resize_index
#
# Returns DDL to rebuild the named index or its partition(s) in the form of:
#
#     ALTER INDEX [schema.]<name> REBUILD
#     [PARTITION <partition>]
#     STORAGE
#     (
#       INITIAL  <bytes>
#       NEXT     <bytes>
#     )
#
sub _resize_index
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $cnt;
  my $sql;
  ( $name, my $partition ) = split /:/, $name;

  my $stmt =
      "
              SELECT
                            count(*) AS cnt
              FROM
                            ${view}_indexes
              WHERE
                                    index_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .= 
      "
                            AND owner      = UPPER('$owner')
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  $cnt = $sth->fetchrow_array;
  die "Index \U$name \Ldoes not exist.\n\n" unless $cnt;

  if ( $partition ) # User wants one partition resized
  {
    $stmt =
      "
              SELECT
                            SUBSTR(segment_type,7)   -- PARTITION or SUBPARTITION
              FROM
                            ${view}_segments      s
              WHERE
                                    s.segment_name   = UPPER( ? )
                            AND s.partition_name = UPPER( ? )
            ";
    if ( $view eq 'DBA' )
    {
      $stmt .=
      "      
                            AND s.owner          = UPPER('$owner')
            ";
    }

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name, $partition );
    my ( $seq_type, $partitioning_type ) = $sth->fetchrow_array;
    die "Partition \U$partition \Lof \UI\Lndex \U$name \Ldoes not exist,\n",
        "  OR it is the parent of Hash subpartition(s)\n",
        "  (i.e., it is not a segment and has no size).\n\n"
        unless $seq_type;

    $sql .= _resize_index_partition(
                                     $schema,
                                     $owner,
                                     $name,
                                     $partition,
                                     $seq_type,
                                     $view
                                   );

    return $sql;
  }

  # Find out if the object is partitioned

  $stmt =
      "
              SELECT
                            partitioned
              FROM
                            ${view}_indexes
              WHERE
                                    index_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .= 
      "
                            AND owner      = UPPER('$owner')
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $partitioned = $sth->fetchrow_array;

  if ( $partitioned eq 'NO' )
  {
    $stmt =
        "
                  SELECT
                                s.blocks
                            , s.initial_extent
                            , s.next_extent
                  FROM
                                ${view}_segments s
                  WHERE
                                        s.segment_name = UPPER( ? )
                                AND s.segment_type = 'INDEX'
                ";
    if ( $view eq 'DBA' )
    {
      $stmt .= 
        "
                                AND s.owner        = UPPER('$owner')
                ";
    }
    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my ( $blocks, $initial, $next ) = $sth->fetchrow_array;

    ( $initial, $next ) = _initial_next( $blocks ) if $attr{ 'resize' };

    $sql .= "PROMPT " .
            "ALTER INDEX \L$schema$name \UREBUILD\n\n" .
            "ALTER INDEX \L$schema$name \UREBUILD\n" .
            "STORAGE\n" .
            "(\n" .
            "  INITIAL  $initial\n" .
            "  NEXT     $next\n" .
            ") ;\n\n";

    return $sql;
  }
  else
  {
    # It's partitioned -- get list of partitions
    # and call _resize_index_partition for each one
    $stmt =
      "
              SELECT
                            partition_name
                        , SUBSTR(segment_type,7)   -- PARTITION or SUBPARTITION
              FROM
                            ${view}_segments
              WHERE
                                    segment_name = UPPER( ? )
            ";
    if ( $view eq 'DBA' )
    {
      $stmt .= 
      " 
                            AND owner        = UPPER('$owner')
            ";
    }
    $stmt .= "
              ORDER
                    BY
                            partition_name
            ";

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my $aref = $sth->fetchall_arrayref;

    foreach my $row ( @$aref )
    {
      my ( $partition, $seg_type ) = @$row;

      $sql .= _resize_index_partition(
                                       $schema,
                                       $owner,
                                       $name,
                                       $partition,
                                       $seg_type,
                                       $view
                                     );
    }

    return $sql;
  }
}

# sub _resize_index_partition
#
# Returns DDL to rebuild one partition of the named object in the form of:
#
#     ALTER INDEX [schema.]<name> REBUILD [SUB]PARTITION <partition>
#     STORAGE
#     (
#       INITIAL  <bytes>
#       NEXT     <bytes>
#     )
#
sub _resize_index_partition
{
  my(
      $schema, $owner, $name, $partition, $seg_type, $view ) = @_;

  my $sql;
  my $stmt =
      "
              SELECT
                            s.blocks
                        , s.initial_extent
                        , s.next_extent
                        , p.partitioning_type
              FROM
                            ${view}_segments      s
                        , ${view}_part_indexes  p
              WHERE
                                    s.segment_name   = UPPER( ? )
                            AND s.partition_name = UPPER( ? )
                            AND p.index_name     = UPPER( ? )
            ";
    if ( $view eq 'DBA' )
    {
      $stmt .= 
      "
                            AND s.owner          = UPPER('$owner')
                            AND p.owner          = UPPER('$owner')
            ";
    }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name, $partition, $name );
  my ( $blocks, $initial, $next, $partitioning_type ) = $sth->fetchrow_array;

  ( $initial, $next ) = _initial_next( $blocks ) if $attr{ 'resize' };

  $sql .= "PROMPT " .
          "ALTER INDEX \L$schema$name \UREBUILD $seg_type \L$partition\n\n" .
          "ALTER INDEX \L$schema$name \UREBUILD $seg_type \L$partition ";

  # Cannot specify storage parameters for a HASH [SUB]PARTITION
  if ( $seg_type eq 'PARTITION' and $partitioning_type eq 'RANGE' )
  {
    $sql .= "\nSTORAGE\n" .
            "(\n" .
            "  INITIAL  $initial\n" .
            "  NEXT     $next\n" .
            ") ";
  }

  $sql .= ";\n\n";

  return $sql;
}

# sub _resize_table
#
# Returns DDL to rebuild the named table or its partition(s) in the form of:
#
#     ALTER TABLE [schema.]<name> MOVE [[SUB]PARTITION <partition>]
#     STORAGE
#     (
#       INITIAL  <bytes>
#       NEXT     <bytes>
#     )
#
sub _resize_table
{
  my ( $schema, $owner, $name, $view ) = @_;

  my $cnt;
  my $sql;
  ( $name, my $partition ) = split /:/, $name;

  my $stmt =
      "
              SELECT
                            count(*) AS cnt
              FROM
                            ${view}_tables
              WHERE
                                    table_name = UPPER( ? )
            ";

  if ( $view eq 'DBA' )
  {
    $stmt .= 
      "
                            AND owner      = UPPER('$owner')
            ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  $cnt = $sth->fetchrow_array;
  die "Table \U$name \Ldoes not exist.\n\n" unless $cnt;

  if ( $partition ) # User wants one partition resized
  {
    $stmt =
      "
              SELECT
                            SUBSTR(segment_type,7)   -- PARTITION or SUBPARTITION
              FROM
                            ${view}_segments
              WHERE
                                    segment_name   = UPPER( ? )
                            AND partition_name = UPPER( ? )
            ";
    if ( $view eq 'DBA' )
    {
      $stmt .=
      "
                            AND owner          = UPPER('$owner')
            ";
    }

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name, $ partition );
    my $type = $sth->fetchrow_array;
    die "Partition \U$partition \Lof \UT\Lable \U$name \Ldoes not exist,\n",
        "  OR it is the parent of Hash subpartition(s)\n",
        "  (i.e., it is not a segment and has no size).\n\n"
        unless $type;

    $sql .= _resize_table_partition(
                                     $schema,
                                     $owner,
                                     $name,
                                     $partition,
                                     $type,
                                     $view
                                   );

    # Rebuild this partition on all LOCAL indexes
    if ( $view eq 'DBA' )
    {
      $stmt = 
        "
                  SELECT
                                owner
                            , index_name
                  FROM
                                dba_part_indexes
                  WHERE
                                        table_name = UPPER( ? )
                                AND owner      = UPPER('$owner')
                                AND locality   = 'LOCAL'
                ";
    }
    else        # We're a mortal USER
    {
      $stmt = 
        "
                  SELECT
                                '$owner'
                            , index_name
                  FROM
                                user_part_indexes
                  WHERE
                                        table_name = UPPER( ? )
                                AND locality   = 'LOCAL'
                ";
    }
  
    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my $aref = $sth->fetchall_arrayref;

    foreach my $row ( @$aref )
    {
      my ( $owner, $index ) = @$row;
      my $schema = _set_schema( $owner );

      $sql .= _resize_index(
                             $schema,
                             $owner,
                             "$index:$partition",
                             $view
                           );
    }
  }
  else
  # Didn't want single partition, so move entire table.
  # First, find out if the object is partitioned
  {
    $stmt =
        "
                  SELECT
                                partitioned
                  FROM
                                ${view}_tables
                  WHERE
                                        table_name = UPPER( ? )
                ";
  
    if ( $view eq 'DBA' )
    {
      $stmt .=
        "
                                AND owner      = UPPER('$owner')
                ";
  
    }
  
    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my $partitioned = $sth->fetchrow_array;
  
    if ( $partitioned eq 'NO' )
    {
      $stmt =
        "
                  SELECT
                                s.blocks - NVL(t.empty_blocks,0)
                            , s.initial_extent
                            , s.next_extent
                  FROM
                                ${view}_segments s
                            , ${view}_tables   t
                  WHERE
                                        s.segment_name = UPPER( ? )
                                AND s.segment_type = 'TABLE'
                                AND t.table_name   = s.segment_name
                ";
      if ( $view eq 'DBA' )
      {
        $stmt .= 
        "
                                    AND s.owner        = UPPER('$owner')
                                    AND t.owner        = s.owner
                ";
      }
      $sth = $dbh->prepare( $stmt );
      $sth->execute( $name );
      my ( $blocks, $initial, $next ) = $sth->fetchrow_array;
  
      ( $initial, $next ) = _initial_next( $blocks ) if $attr{ 'resize' };
  
      $sql .= "PROMPT " .
              "ALTER TABLE \L$schema$name \UMOVE\n\n" .
              "ALTER TABLE \L$schema$name \UMOVE\n" .
              "STORAGE\n" .
              "(\n" .
              "  INITIAL  $initial\n" .
              "  NEXT     $next\n" .
              ") ;\n\n";
  
      return $sql;
    }
    else
    {
      # It's partitioned -- get list of partitions
      # and call _resize_table_partition for each one
      $stmt =
        "
                  SELECT
                                partition_name
                            , SUBSTR(segment_type,7)   -- PARTITION or SUBPARTITION
                  FROM
                                ${view}_segments
                  WHERE
                                        segment_name = UPPER( ? )
                ";
      if ( $view eq 'DBA' )
      {
        $stmt .= 
        " 
                                AND owner        = UPPER('$owner')
                ";
      }
      $stmt .=
        "
                  ORDER
                        BY
                                partition_name
                ";
  
      $sth = $dbh->prepare( $stmt );
      $sth->execute( $name );
      my $aref = $sth->fetchall_arrayref;
  
      foreach my $row ( @$aref )
      {
        my ( $partition, $type ) = @$row;
  
        $sql .= _resize_table_partition(
                                         $schema,
                                         $owner,
                                         $name,
                                         $partition,
                                         $type,
                                         $view
                                       );
      }
    }

    # Rebuild all indexes (partitioned or not)
    if ( $view eq 'DBA' )
    {
      $stmt = 
        "
                  SELECT
                                owner
                            , index_name
                  FROM
                                dba_part_indexes
                  WHERE
                                        table_name = UPPER( ? )
                                AND owner      = UPPER('$owner')
                ";
    }
    else        # We're a mortal USER
    {
      $stmt = 
        "
                  SELECT
                                '$owner'
                            , index_name
                  FROM
                                user_part_indexes
                  WHERE
                                        table_name = UPPER( ? )
                ";
    }
  
    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my $aref = $sth->fetchall_arrayref;

    foreach my $row ( @$aref )
    {
      my ( $owner, $index ) = @$row;
      my $schema = _set_schema( $owner );

      $sql .= _resize_index(
                             $schema,
                             $owner,
                             $index,
                             $view
                           );
    }
  }

  return $sql;
}

# sub _resize_table_partition
#
# Returns DDL to rebuild one partition of the named object in the form of:
#
#     ALTER TABLE [schema.]<name> MOVE [SUB]PARTITION <partition>
#     STORAGE
#     (
#       INITIAL  <bytes>
#       NEXT     <bytes>
#     )
#
sub _resize_table_partition
{
  my( $schema, $owner, $name, $partition, $seg_type, $view ) = @_;

  my $sql;
  my $stmt =
      "
              SELECT
                            s.blocks - NVL(t.empty_blocks,0)
                        , s.initial_extent
                        , s.next_extent
                        , p.partitioning_type
              FROM
                            ${view}_segments          s
                        , ${view}_tab_${seg_type}s  t
                        , ${view}_part_tables       p
              WHERE
                                    s.segment_name   = UPPER( ? )
                            AND s.partition_name = UPPER( ? )
                            AND t.table_name     = UPPER( ? )
                            AND t.partition_name = UPPER( ? )
                            AND p.table_name     = UPPER( ? )
            ";
    if ( $view eq 'DBA' )
    {
      $stmt .= 
      "
                            AND s.owner          = UPPER('$owner')
                            AND p.owner          = UPPER('$owner')
                            AND t.table_owner    = UPPER('$owner')
            ";
    }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name, $partition, $name, $partition, $name );
  my ( $blocks, $initial, $next, $partitioning_type ) = $sth->fetchrow_array;

  ( $initial, $next ) = _initial_next( $blocks ) if $attr{ 'resize' };

  $sql .= "PROMPT " .
          "ALTER TABLE \L$schema$name \UMOVE $seg_type \L$partition\n\n" .
          "ALTER TABLE \L$schema$name \UMOVE $seg_type \L$partition ";

  # Cannot specify storage parameters for a HASH [SUB]PARTITION
  if ( $seg_type eq 'PARTITION' and $partitioning_type eq 'RANGE' )
  {
    $sql .= "\nSTORAGE\n" .
            "(\n" .
            "  INITIAL  $initial\n" .
            "  NEXT     $next\n" .
            ") ";
  }

  $sql .= ";\n\n";

  return $sql;
}

# sub _scratch_prompts
#
# Eliminates all PROMPT statements
#
sub _scratch_prompts
{
  # Drop all lines beginning with PROMPT
  $ddl = ( join "\n",grep !/^PROMPT/,split /\n/,$ddl );

  # This would have left the first line blank, so drop it
  $ddl =~ s|\A\n||;

  # Get rid of double blank lines
  $ddl =~ s|\n\n+|\n\n|g;
}

# sub _segments_attributes
#
# Formats the segment attributes portion of CREATE TABLE and CREATE INDEX
# statements
#
sub _segment_attributes
{
  my ( $arrayref ) = @_;

  my $sql;
  my (
       $indent,
       # Physical Properties
       $organization,
       # Segment Attributes
       $cache,
       $pct_used,
       $pct_free,
       $ini_trans,
       $max_trans,
       $initial,
       $next,
       $min_extents,
       $max_extents,
       $pct_increase,
       $freelists,
       $freelist_groups,
       $buffer_pool,
       $logging,
       $tablespace,
       $blocks,
     ) = @$arrayref;

  unless ( $attr{ 'storage' } )
  {
    if ( $attr{ 'tblspace' } )
    {
      $sql .= "${indent}TABLESPACE          \L$tablespace\n";
    }

    return $sql;
  }

  ( $initial, $next ) = _initial_next( $blocks ) if $attr{ 'resize' };

  if ( $organization eq 'HEAP' )
  {
    $sql = "${indent}$cache\n"       unless $cache eq 'N/A';
    $sql .="${indent}PCTUSED             $pct_used\n"    unless $isasnapindx;
  }

  $sql .= "${indent}PCTFREE             $pct_free\n"     unless $isasnapindx;
 
  $sql .= "${indent}INITRANS            $ini_trans\n"    unless $isasnaptabl; 

  $sql .= "${indent}MAXTRANS            $max_trans\n" .
          "${indent}STORAGE\n" .
          "${indent}(\n" .
          "${indent}  INITIAL           $initial\n" .
          "${indent}  NEXT              $next\n" .
          "${indent}  MINEXTENTS        $min_extents\n" .
          "${indent}  MAXEXTENTS        $max_extents\n" .
          "${indent}  PCTINCREASE       $pct_increase\n";

    if (
            $oracle_major < 8
         or ( $oracle_major == 8 and $oracle_minor == 0 )
       )
    {
      unless (
                  $isasnaptabl
               or $isasnapindx
             )
      {
        $sql .= "${indent}  FREELISTS         $freelists\n" .
                "${indent}  FREELIST GROUPS   $freelist_groups\n";
      }
    }
    else
    {
      $sql .= "${indent}  FREELISTS         $freelists\n" .
              "${indent}  FREELIST GROUPS   $freelist_groups\n";
    }


    if (
            $oracle_major > 8
         or ( $oracle_major == 8 and $oracle_minor > 0 )
       )
    {
      $sql .= "${indent}  BUFFER_POOL       $buffer_pool\n";
    }

  $sql .= "${indent})\n";

  $sql .= "${indent}$logging\n"    if $oracle_major > 7;

  if ( $attr{ 'tblspace' } )
  {
    $sql .= "${indent}TABLESPACE          \L$tablespace\n";
  }

  return $sql;
}

sub _set_schema
{
  my $owner = shift;

  my $schema = ( "\U$owner" eq 'PUBLIC' ) ? 'PUBLIC'               :
               ( $attr{ schema } == 1   ) ? $owner . '.'           :
               ( $attr{ schema } )        ? $attr{ schema }  . '.' : '';

  return $schema;
}

# sub _set_sizing
#
# If %attr has an entry for "resize" == l, generates an arbitrary sizing
# algorithm wherein the database block size is used to create an array such
# that each object will have no more than 8 extents.  The INITIAL and NEXT
# sizes of Tables and Indexes are set to the calculated value.
#
# If %attr DOES contain an entry for "resize", it is parsed and stored in the
# array called @size_arr.
#
sub _set_sizing 
{
  $sth = $dbh->prepare(
      "
              SELECT
                            block_size
              FROM
                            (
                                SELECT
                                              bytes / blocks   AS block_size
                                FROM
                                              user_segments
                                WHERE
                                                      bytes  IS NOT NULL
                                              AND blocks IS NOT NULL
                                              AND rownum  < 2
                                UNION
                                SELECT
                                              bytes / blocks   AS block_size
                                FROM
                                              user_free_space
                                WHERE
                                                      bytes  IS NOT NULL
                                              AND blocks IS NOT NULL
                                              AND rownum  < 2
                            )
              WHERE
                            rownum < 2
            ");

  $sth->execute;
  $block_size = $sth->fetchrow_array / 1024;

  if ( $attr{ 'resize' } == 1 )
  {
    # Create default array
    for my $i ( 0 .. 4 )
    {
      my $limit = ( 4 * ( 10 ** ( $i + 1 ) ) + 1 );
      my $initial = my $next = ( 5 * $block_size ) * ( 10 ** $i ) . "K";
      push @size_arr, [$limit, $initial, $next];
    }
    # Force upper limit bound
    $size_arr[$#size_arr][0] = 'UNLIMITED';
  }
  elsif ( $attr{ 'resize' } )
  {
    # parse user supplied string into @size_arr
    my $remainder = $attr{ 'resize' };
    while ( $remainder ) 
    {
      ( my ($limit,$initial,$next),$remainder ) = split /:/, $remainder, 4;

      die "\nSupplied resize string is malformed.\n\n" unless $initial;
      die "\nSupplied resize string is malformed.\n\n" unless $next;

      push @size_arr, [$limit, $initial, $next];
    }
    # Force upper limit bound
    $size_arr[$#size_arr][0] = 'UNLIMITED';
  }
}

#sub _show_free_space
#
# Reutrns a report in the form of:
#
# Space analysis for: [schema.]<object name>
# 
#                                                  BYTES     BLOCKS
#                                           ============  =========
# Used BELOW the high water mark                 1325056        647
# Free ABOVE the high water mark                  716800        350
# Free BELOW the high water mark                    6144          3
#                                           ------------  ---------
#               TOTAL in segment                 2048000       1000
# 
#                                      FILE_ID  BLOCK_ID  BLOCK_NBR
#                                      =======  ========  =========
# Last extent having data                    9     10287        150
#
sub _show_free_space
{
  my ( $owner, $name, $type, $view ) = @_;

  my $stmt =
      "
              SELECT
                            'Yes, I can execute package DBMS_SPACE'
              FROM
                            all_tab_privs
              WHERE
                                    privilege    = 'EXECUTE'
                            AND table_name   = 'DBMS_SPACE'
                            AND table_schema = 'SYS'
            ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  my ( $can_execute ) = $sth->fetchrow_array;
  die "Either you or PUBLIC must have EXECUTE privilege on package\n",
      "sys.DBMS_SPACE in order to produce the FREE SPACE report.\n\n"
    unless $can_execute;

  if ( $oracle_major == 7 or "\U$type" eq 'CLUSTER' )
  {
    $stmt =
      "
              SELECT
                            'NO'                    AS partitioned
              FROM
                            ${view}_segments
              WHERE
                                    segment_name = UPPER( ? )
                            AND segment_type = UPPER( '$type' )
            ";
  }
  else
  {
    my $plural = ( "\U$type" eq 'INDEX' ) ? 'ES' : 'S';

    $stmt =
      "
              SELECT
                            partitioned
              FROM
                            ${view}_$type$plural
              WHERE
                                    ${type}_name = UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
      $stmt .=
        "
                            AND owner       = UPPER('$owner')
                ";
  }

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my ( $partitioned ) = $sth->fetchrow_array;
  die "\u\L$type \U$name \Ldoes not exist.\n\n" unless $partitioned;

  my $text  = "Space analysis for: \U$owner.$name\n\n";

  if ( $partitioned eq 'YES' )
  {
    my (
         $section,
         $total_blocks,
         $total_bytes,
         $unused_blocks,
         $unused_bytes,
         $free_blocks,
         $sum_total_blocks,
         $sum_total_bytes,
         $sum_unused_blocks,
         $sum_unused_bytes,
         $sum_free_blocks,
       );

    $stmt =
      "
              SELECT
                            RPAD(partition_name,30)
                        , segment_type
              FROM
                            ${view}_segments
              WHERE
                                    segment_name = UPPER( ? )
            ";

    if ( $view eq 'DBA' )
    {
      $stmt .=
        "
                            AND owner       = UPPER('$owner')
                ";
    }

    $stmt .=
      "
              ORDER
                    BY
                            partition_name
            ";

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $name );
    my $aref = $sth->fetchall_arrayref;

    foreach my $row (@$aref )
    {
      my ( $partition, $seg_type ) = @$row;

      (
        $section,
        $total_blocks,
        $total_bytes,
        $unused_blocks,
        $unused_bytes,
        $free_blocks,
      ) = _print_space( $owner, $name, $seg_type, $partition );

      $text .= $section;

      $sum_total_blocks  += $total_blocks;
      $sum_total_bytes   += $total_bytes;
      $sum_unused_blocks += $unused_blocks;
      $sum_unused_bytes  += $unused_bytes;
      $sum_free_blocks   += $free_blocks;
    }

    $text .= "TOTAL segment                                    BYTES" .
             "     BLOCKS\n" .
             "                                          ============" .
             "  =========\n" .
             sprintf(
                      "Used BELOW the high water mark            %12d  %9d\n",
                      $sum_total_bytes - $sum_unused_bytes 
                        - $sum_free_blocks * $block_size * 1024,
                      $sum_total_blocks - $sum_unused_blocks - $sum_free_blocks
                    ) .
             sprintf(
                      "Free ABOVE the high water mark            %12d  %9d\n",
                      $sum_unused_bytes, $sum_unused_blocks
                    ) .
             sprintf(
                      "Free BELOW the high water mark            %12d  %9d\n",
                      $sum_free_blocks * $block_size * 1024, $sum_free_blocks
                    ) .
             "                                          ------------" .
             "  ---------\n" .
             sprintf(
                      "        GRAND TOTAL in segment            %12d  %9d\n\n",
                      $sum_total_bytes, $sum_total_blocks
                    );

    return $text;
  }
  else  # Not partitioned
  {
    ( my $section ) = _print_space( $owner, $name, $type );
    return $text . $section;
  }
}

#sub _subpartition_key_columns
#
# Returns a formatted string containing the subpartitioning key columns
#
sub _subpartition_key_columns
{
  return _key_columns( @_, 'SUBPART' );
}

# sub _table_columns
#
# Returns a formatted string containing the column names, datatype and
# length, and NOT NULL (if appropriate) for use in a CREATE TABLE
# statement.
#
sub _table_columns
{
  my ( $owner, $name, $view ) = @_;

  my $stmt;

  if ( $oracle_major == 7 )
  {
    $stmt =
      "
              SELECT
                            RPAD(LOWER(column_name),32)
                      || RPAD(
                                      DECODE(
                                                      data_type
                                                    ,'NUMBER',DECODE(
                                                                                        data_precision
                                                                                      ,null,DECODE(
                                                                                                                  data_scale
                                                                                                                ,0,'INTEGER'
                                                                                                                ,  'NUMBER   '
                                                                                                              )
                                                                                      ,'NUMBER   '
                                                                                    )
                                                    ,'RAW'     ,'RAW      '
                                                    ,'CHAR'    ,'CHAR     '
                                                    ,'NCHAR'   ,'NCHAR    '
                                                    ,'UROWID'  ,'UROWID   '
                                                    ,'VARCHAR2','VARCHAR2 '
                                                    ,data_type
                                                  )
                                          || DECODE(
                                                                data_type
                                                              ,'DATE',null
                                                              ,'LONG',null
                                                              ,'NUMBER',DECODE(
                                                                                                  data_precision
                                                                                                ,null,null
                                                                                                ,'('
                                                                                              )
                                                              ,'RAW'      ,'('
                                                              ,'CHAR'     ,'('
                                                              ,'NCHAR'    ,'('
                                                              ,'UROWID'   ,'('
                                                              ,'VARCHAR2' ,'('
                                                              ,'NVARCHAR2','('
                                                              ,null
                                                            )
                                          || DECODE(
                                                                data_type
                                                              ,'RAW'      ,data_length
                                                              ,'CHAR'     ,data_length
                                                              ,'NCHAR'    ,data_length
                                                              ,'UROWID'   ,data_length
                                                              ,'VARCHAR2' ,data_length
                                                              ,'NVARCHAR2',data_length
                                                              ,'NUMBER'   ,data_precision
                                                              , null
                                                            )
                                          || DECODE(
                                                                data_type
                                                              ,'NUMBER',DECODE(
                                                                  data_precision
                                                                ,null,null
                                                                ,DECODE(
                                                                                  data_scale
                                                                                ,null,null
                                                                                ,0   ,null
                                                                                ,',' || data_scale
                                                                              )
                                                                        )
                                                            )
                                          || DECODE(
                                                                data_type
                                                              ,'DATE',null
                                                              ,'LONG',null
                                                              ,'NUMBER',DECODE(
                                                                                                  data_precision
                                                                                                ,null,null
                                                                                                ,')'
                                                                                              )
                                                              ,'RAW'      ,')'
                                                              ,'CHAR'     ,')'
                                                              ,'NCHAR'    ,')'
                                                              ,'UROWID'   ,')'
                                                              ,'VARCHAR2' ,')'
                                                              ,'NVARCHAR2',')'
                                                              ,null
                                                            )
                                      ,33
                                    )
                        , nullable
                        , data_default
              FROM
                            ${view}_tab_columns
              WHERE
                                    table_name = UPPER( ? )
            ";
  }
  else                  # We're newer than Oracle7
  {
    $stmt =
      "
              SELECT
                            RPAD(LOWER(column_name),32)
                      || RPAD(
                                      DECODE(
                                                      data_type
                                                    ,'NUMBER',DECODE(
                                                                                        data_precision
                                                                                      ,null,DECODE(
                                                                                                                  data_scale
                                                                                                                ,0,'INTEGER'
                                                                                                                ,  'NUMBER   '
                                                                                                              )
                                                                                      ,'NUMBER   '
                                                                                    )
                                                    ,'RAW'     ,'RAW      '
                                                    ,'CHAR'    ,'CHAR     '
                                                    ,'NCHAR'   ,'NCHAR    '
                                                    ,'UROWID'  ,'UROWID   '
                                                    ,'VARCHAR2','VARCHAR2 '
                                                    ,data_type
                                                  )
                                          || DECODE(
                                                                data_type
                                                              ,'DATE',null
                                                              ,'LONG',null
                                                              ,'NUMBER',DECODE(
                                                                                                  data_precision
                                                                                                ,null,null
                                                                                                ,'('
                                                                                              )
                                                              ,'RAW'      ,'('
                                                              ,'CHAR'     ,'('
                                                              ,'NCHAR'    ,'('
                                                              ,'UROWID'   ,'('
                                                              ,'VARCHAR2' ,'('
                                                              ,'NVARCHAR2','('
                                                              ,null
                                                            )
                                          || DECODE(
                                                                data_type
                                                              ,'RAW'      ,data_length
                                                              ,'CHAR'     ,data_length
                                                              ,'NCHAR'    ,data_length
                                                              ,'UROWID'   ,data_length
                                                              ,'VARCHAR2' ,data_length
                                                              ,'NVARCHAR2',data_length
                                                              ,'NUMBER'   ,data_precision
                                                              , null
                                                            )
                                          || DECODE(
                                                                data_type
                                                              ,'NUMBER',DECODE(
                                                                  data_precision
                                                                ,null,null
                                                                ,DECODE(
                                                                                  data_scale
                                                                                ,null,null
                                                                                ,0   ,null
                                                                                ,',' || data_scale
                                                                              )
                                                                        )
                                                            )
                                          || DECODE(
                                                                data_type
                                                              ,'DATE',null
                                                              ,'LONG',null
                                                              ,'NUMBER',DECODE(
                                                                                                  data_precision
                                                                                                ,null,null
                                                                                                ,')'
                                                                                              )
                                                              ,'RAW'      ,')'
                                                              ,'CHAR'     ,')'
                                                              ,'NCHAR'    ,')'
                                                              ,'UROWID'   ,')'
                                                              ,'VARCHAR2' ,')'
                                                              ,'NVARCHAR2',')'
                                                              ,null
                                                            )
                                      ,32
                                    )
                        , nullable
                        , data_default
              FROM
                            ${view}_tab_columns
              WHERE
                                    table_name = UPPER( ? )
            ";
  }

  if ( $view eq 'DBA' )
  {
    $stmt .=
      "
                            AND owner      = UPPER('$owner')
            ";

  }

  $stmt .= 
      "
              ORDER
                    BY
                          column_id
            ";

  $dbh->{ LongReadLen } = 1024;    # Allows Default to be 1K
  $dbh->{ LongTruncOk } = 1;

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $name );
  my $aref = $sth->fetchall_arrayref;

  my @cols;
  foreach my $row ( @$aref )
  {
    my ( $column, $nullable, $default ) = @$row;

    # Knock off any trailing newlines
    chomp $default;

    $column .= "DEFAULT $default "   if defined $default;
    $column .= 'NOT NULL'            if $nullable eq 'N';

    push @cols, $column;
  }

  return join ( "\n  , ", @cols ) . "\n";
}

1;

__END__

########################################################################