home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / xampp / xampp-perl-addon-1.4.9-installer.exe / defrag.pl < prev    next >
Encoding:
Perl Script  |  2002-06-14  |  80.7 KB  |  2,703 lines

  1. #! /usr/bin/perl -w
  2.  
  3. # $Id: defrag.pl,v 1.18 2001/04/28 13:51:28 rvsutherland Exp $
  4. #
  5. # Copyright (c) 2000, 2001 Richard Sutherland - United States of America
  6. #
  7. # See COPYRIGHT section in pod text below for usage and distribution rights.
  8.  
  9. use Cwd;
  10. use DBI;
  11. use DDL::Oracle;
  12. use English;
  13. use Getopt::Long;
  14.  
  15. use strict;
  16.  
  17. my %args;
  18. my %uniq;
  19.  
  20. my @constraints;
  21. my @export_objects;
  22. my @export_temps;
  23. my @logfiles;
  24. my @perf_tables = ( 
  25.                    'DBA_ALL_TABLES',
  26.                    'DBA_INDEXES', 
  27.                    'DBA_PART_INDEXES',
  28.                    'DBA_PART_TABLES',
  29.                    'DBA_SEGMENTS', 
  30.                    'DBA_TABLES', 
  31.                    'THE_CONSTRAINTS',
  32.                    'THE_IOTS',
  33.                    'THE_INDEXES',
  34.                    'THE_PARTITIONS',
  35.                    'THE_TABLES',
  36.                   );
  37. my @sizing_array;
  38.  
  39. my $add_ndx_log;
  40. my $add_tbl_log;
  41. my $add_temp_log;
  42. my $add_temp_sql;
  43. my $alttblsp;
  44. my $aref;
  45. my $create_ndx_ddl;
  46. my $create_tbl_ddl;
  47. my $create_temp_ddl;
  48. my $date;
  49. my $dbh;
  50. my $drop_all_log;
  51. my $drop_ddl;
  52. my $drop_temp_ddl;
  53. my $drop_temp_log;
  54. my $drop_temp_sql;
  55. my $expdir;
  56. my $exp_log;
  57. my $header10;
  58. my $home = $ENV{HOME}
  59.         || $ENV{LOGDIR}
  60.         || ( getpwuid( $REAL_USER_ID ) )[7]
  61.         || die "\nCan't determine HOME directory.\n";
  62. my $imp_log;
  63. my $logdir;
  64. my $obj;
  65. my $other_constraints;
  66. my $partitions;
  67. my $prefix;
  68. my $prttn_exp_log;
  69. my $prttn_exp_par;
  70. my $prttn_exp_text;
  71. my $prttn_imp_log;
  72. my $prttn_imp_par;
  73. my $prttn_imp_text;
  74. my $row;
  75. my $script;
  76. my $sqldir;
  77. my $sth;
  78. my $stmt;
  79. my $tblsp;
  80. my $text;
  81. my $user = getlogin
  82.         || scalar getpwuid( $REAL_USER_ID )
  83.         || undef
  84.      unless $OSNAME eq 'MSWin32';
  85. $user = 'Unknown User'    unless $user;
  86.  
  87. ########################################################################
  88.  
  89. set_defaults();
  90.  
  91. if (
  92.          @ARGV    == 0
  93.      or  $ARGV[0] eq "?"
  94.      or  $ARGV[0] eq "-?"
  95.      or  $ARGV[0] eq "-h"
  96.      or  $ARGV[0] eq "--help"
  97.    ) 
  98. {
  99.   print_help();
  100.   exit 0;
  101. }
  102.  
  103. print "\n$0 is being executed by $user\non ", scalar localtime,"\n\n";
  104. get_args();
  105. print "Generating files to defrag Tablespace $tblsp.\n",
  106.       "Using Tablespace $alttblsp for partition operations.\n\n";
  107. initialize_queries();
  108.  
  109. #
  110. # Display user options, and save them in .defrag.rc
  111. #
  112.  
  113. delete $args{ sid } if $args{ sid } eq "";
  114. open RC, ">$home/.defragrc" or die "Can't open $home/.defragrc:  $!\n";
  115. KEY:
  116.   foreach my $key ( sort keys %args ) 
  117.   {
  118.     next KEY unless (
  119.                          $key eq "sid"
  120.                       or $key eq "logdir"
  121.                       or $key eq "sqldir"
  122.                       or $key eq "prefix"
  123.                       or $key eq "expdir"
  124.                       or $key eq "resize"
  125.                     );
  126.     print "$key = $args{ $key }\n";
  127.     print RC "$key = $args{ $key }\n";
  128.   }
  129. close RC or die "Can't close $home/.defragrc:  $!\n";
  130.  
  131. print "\nWorking...\n\n";
  132.  
  133. ########################################################################
  134.  
  135. #
  136. # Now we're ready -- start dafriggin' defraggin'
  137. #
  138.  
  139. # The 10 steps below issue queries mostly comprised of 5 main queries,
  140. # sometimes doing UNIONs and/or MINUSes among them.  The query results
  141. # are stored in temporary tables for performance reasons.
  142. #
  143. # See sub 'initialize_queries' for the queries and their descriptions.
  144. #
  145.  
  146. # Step 1 - Export the stray partitions -- those in our tablespace whose
  147. #          table also has partitions in at least one other tablespace.
  148. #          If said partitions exist, there will be 2 exports.  After the
  149. #          first export, for each such partition:
  150. #            a) Create a Temp table mirroring the partition.
  151. #            b) Create indexes on the Temp table matching the LOCAL 
  152. #               indexes on the partitioned table.
  153. #            c) Create a PK matching the PK of the partitioned table,
  154. #               if any.
  155. #            d) EXCHANGE the Temp table with the partition.
  156. #            e) MOVE the [now empty] partition to the alternate tablespace.
  157. #
  158. #          With the data now in the Temp table, the Temp table gets 
  159. #          treated the same as other regular tables in our tablespace
  160. #          (see Steps 2 - 9), but has added operations following the
  161. #          creation of its indexes (same as the LOCAL indexes on the 
  162. #          partition) and the addition of its PK (if any).
  163. #
  164. #            a) the Temp table does an EXCHANGE PARTITION so that the
  165. #               data (which was imported into the Temp table) rejoins
  166. #               the partitioned table.
  167. #            b) the [now empty] Temp table is DROPped.
  168. #
  169. #            c) REBUILD all Global indexes (if any) on the partitioned
  170. #               table(s).
  171. #
  172. #          NOTE:  Two 'fall back' scripts are created which are to be
  173. #                 used ONLY in the event that problems occur during
  174. #                 Step 1 (Shell #2 when such partitions exist).
  175. #
  176. #                  ***  DO NOT PROCEED IF Shell #2 HAS ERRORS ***
  177. #
  178. #                 Shells #8 and #9  will restore the data to the original
  179. #                 condition Their Steps are:
  180. #                   a) DROP the Temp table(s).
  181. #                   b) TRUNCATE the partitions
  182. #                   c) MOVE the partitions back to our tablespace
  183. #                   d) Import the data back into the partitions.
  184. #
  185.  
  186. $stmt =
  187.       "
  188.        SELECT
  189.               owner
  190.             , segment_name
  191.             , partition_name
  192.             , segment_type
  193.             , partitioning_type
  194.             , analyzed
  195.        FROM
  196.               THE_PARTITIONS
  197.        ORDER
  198.           BY
  199.               1, 2, 3
  200.       ";
  201.  
  202. $sth = $dbh->prepare( $stmt );
  203. $sth->execute;
  204. $aref = $sth->fetchall_arrayref;
  205.  
  206. foreach $row ( @$aref )
  207. {
  208.   my ( 
  209.        $owner, 
  210.        $table, 
  211.        $partition, 
  212.        $type, 
  213.        $partitioning_type,
  214.        $analyzed
  215.      ) = @$row;
  216.  
  217.   $obj = DDL::Oracle->new(
  218.                            type => 'exchange table',
  219.                            list => [
  220.                                      [
  221.                                        "$owner",
  222.                                        "$table:$partition",
  223.                                      ]
  224.                                    ],
  225.                          );
  226.   my $create_tbl = $obj->create;
  227.   # Remove REM lines created by DDL::Oracle
  228.   $create_tbl = ( join "\n",grep !/^REM/,split /\n/,$create_tbl )."\n\n";
  229.  
  230.   my $temp = "${tblsp}_${date}_" . unique_nbr();
  231.  
  232.   push @export_temps,   "\L$owner.$table:$partition";
  233.   push @export_objects, "\L$owner.$temp";
  234.  
  235.   # Change the CREATE TABLE statement to create the temp
  236.   my $ownr    = escaped_dollar_signs( $owner );
  237.   my $tabl    = escaped_dollar_signs( $table );
  238.   $create_tbl =~ s|\L$ownr.$tabl|\L$owner.$temp|g;
  239.  
  240.   my $exchange = index_and_exchange( $temp, @$row );
  241.  
  242.   $obj = DDL::Oracle->new(
  243.                            type => 'table',
  244.                            list => [
  245.                                      [
  246.                                        "$owner",
  247.                                        "$temp",
  248.                                      ]
  249.                                    ],
  250.                          );
  251.   my $drop_tbl = $obj->drop;
  252.   # Remove REM lines created by DDL::Oracle
  253.   $drop_tbl = ( join "\n", grep !/^REM/, split /\n/, $drop_tbl ) . "\n\n";
  254.  
  255.   $obj = DDL::Oracle->new(
  256.                            type => 'table',
  257.                            list => [
  258.                                      [
  259.                                        "$owner",
  260.                                        "$table:$partition",
  261.                                      ]
  262.                                    ],
  263.                          );
  264.   my $resize = $obj->resize;
  265.   # Remove REM lines created by DDL::Oracle
  266.   $resize =  ( join "\n", grep !/^REM/, split /\n/, $resize ) . "\n\n";
  267.   $resize =~ s|\;|\nTABLESPACE \L$tblsp \;\n\n|;
  268.  
  269.   my $drop_temp =  $drop_tbl .
  270.                    trunc( @$row ) .
  271.                    $resize;
  272.  
  273.   $create_temp_ddl  = group_header( 1 )   unless $create_temp_ddl;
  274.   $create_temp_ddl .= $create_tbl .
  275.                       $exchange .
  276.                       move( @$row, $alttblsp );
  277.  
  278.   $drop_ddl         = group_header( 2 )   unless $drop_ddl;
  279.   $drop_ddl        .= $drop_tbl;
  280.  
  281.   $create_tbl_ddl   = group_header( 7 )   unless $create_tbl_ddl;
  282.   $create_tbl_ddl  .= $create_tbl;
  283.  
  284.   $create_ndx_ddl   = group_header( 9 )   unless $create_ndx_ddl;
  285.   $create_ndx_ddl  .= $exchange .
  286.                       $drop_tbl;      
  287.  
  288.   $drop_temp_ddl    = group_header( 15 )  unless $drop_temp_ddl;
  289.   $drop_temp_ddl   .= $drop_temp;
  290. }
  291.  
  292. #
  293. # Step 2 - Drop all Foreign Keys referenceing our tables and IOT's or
  294. #          referenceing the tables of our other indexes.  NOTE:  our
  295. #          indexes may not be the target of a foreign key, but for 
  296. #          simplicity purposes if the index's table holds said target
  297. #          (i.e., its index is in some other tablespace but it belongs
  298. #          to the same table as our index), we'll drop the FK anyway --
  299. #          it won't hurt anything and we promise to put it back.
  300. #
  301.  
  302. $stmt =
  303.       "
  304.        SELECT --+ use_hash(c r)
  305.               c.owner
  306.             , c.constraint_name
  307.        FROM
  308.               THE_CONSTRAINTS  c
  309.             , THE_CONSTRAINTS  r
  310.        WHERE
  311.                   c.constraint_type   = 'R'
  312.               AND c.r_owner           = r.owner
  313.               AND c.r_constraint_name = r.constraint_name
  314.               AND (
  315.                       r.owner
  316.                     , r.table_name
  317.                   ) IN (
  318.                          SELECT
  319.                                 owner
  320.                               , table_name
  321.                          FROM
  322.                                 THE_TABLES
  323.                          UNION ALL
  324.                          SELECT
  325.                                 owner
  326.                               , table_name
  327.                          FROM
  328.                                 THE_IOTs
  329.                          UNION ALL
  330.                          SELECT
  331.                                 owner
  332.                               , table_name
  333.                          FROM
  334.                                 THE_INDEXES
  335.                        )
  336.        ORDER
  337.           BY
  338.               1, 2
  339.       ";
  340.  
  341. $sth = $dbh->prepare( $stmt );
  342. $sth->execute;
  343. my $fk_aref = $sth->fetchall_arrayref;
  344.  
  345. $obj = DDL::Oracle->new(
  346.                          type => 'constraint',
  347.                          list => $fk_aref,
  348.                        );
  349.  
  350. $drop_ddl .= group_header( 3 ) . $obj->drop    if @$fk_aref;
  351.  
  352. #
  353. # Step 3 - Drop and create the tables.  NOTE:  the DROP statements are in
  354. #          one file followed by COALESCE tablespace statements, and the
  355. #          CREATE statements are put in a separate file.  The assumption
  356. #          here is that the user will verify that the DROP and COALESCE
  357. #          statements executed OK before executing the CREATE tables file.
  358. #
  359.  
  360. $stmt =
  361.       "
  362.        SELECT DISTINCT
  363.               owner
  364.             , table_name
  365.             , analyzed
  366.        FROM
  367.             (
  368.               SELECT
  369.                      owner
  370.                    , table_name
  371.                    , analyzed
  372.               FROM
  373.                      THE_TABLES
  374.               UNION ALL
  375.               SELECT
  376.                      owner
  377.                    , table_name
  378.                    , analyzed
  379.               FROM
  380.                      THE_IOTs
  381.             )
  382.        ORDER
  383.           BY
  384.               1, 2
  385.       ";
  386.  
  387. $sth = $dbh->prepare( $stmt );
  388. $sth->execute;
  389. $aref = $sth->fetchall_arrayref;
  390.  
  391. if ( @$aref )
  392. {
  393.   $obj = DDL::Oracle->new(
  394.                            type => 'table',
  395.                            list => $aref,
  396.                          );
  397.  
  398.   $drop_ddl       .= group_header( 4 ) . $obj->drop;
  399.  
  400.   $create_tbl_ddl .= group_header( 8 ) . $obj->create;
  401.  
  402.   foreach $row ( @$aref )
  403.   {
  404.     my ( $owner, $table, $analyzed ) = @$row;
  405.  
  406.     push @export_objects, "\L$owner.$table";
  407.  
  408.     if ( $analyzed eq 'YES' )
  409.     {
  410.       $create_ndx_ddl .= group_header( 10 )    unless $header10++;
  411.  
  412.       $create_ndx_ddl .= "PROMPT " .
  413.                          "ANALYZE TABLE \L$owner.$table\n\n" .
  414.                          "ANALYZE TABLE \L$owner.$table " .
  415.                          "ESTIMATE STATISTICS ;\n\n";
  416.     }
  417.   }
  418.  
  419. }
  420.  
  421. #
  422. # Step 4 - Drop all Primary Key, Unique and Check constraints on the tables
  423. #          of our indexes (those on our tables disappeared with the DROP
  424. #          TABLE statements).
  425. #
  426.  
  427. $stmt =
  428.       "
  429.        SELECT
  430.               owner
  431.             , constraint_name
  432.        FROM
  433.               THE_CONSTRAINTS
  434.        WHERE
  435.                   constraint_type IN ('P','U','C')
  436.               AND (
  437.                       owner
  438.                     , table_name
  439.                   ) IN (
  440.                          SELECT
  441.                                 owner
  442.                               , table_name
  443.                          FROM
  444.                                 THE_INDEXES
  445.                          MINUS
  446.                          (
  447.                            SELECT
  448.                                   owner
  449.                                 , table_name
  450.                            FROM
  451.                                   THE_TABLES
  452.                            UNION ALL
  453.                            SELECT
  454.                                   owner
  455.                                 , table_name
  456.                            FROM
  457.                                   THE_IOTs
  458.                          )
  459.                        )
  460.        ORDER
  461.           BY
  462.               1, 2
  463.       ";
  464.  
  465. $sth = $dbh->prepare( $stmt );
  466. $sth->execute;
  467. $aref = $sth->fetchall_arrayref;
  468.  
  469. $obj = DDL::Oracle->new(
  470.                          type => 'constraint',
  471.                          list => $aref,
  472.                        );
  473.  
  474. $drop_ddl .= group_header( 5 ) . $obj->drop    if @$aref;
  475.  
  476. #
  477. # Step 5 - Drop all of our indexes, unless they are the supporting index
  478. #          of a Primary Key or Unique constraint -- these disappeared in
  479. #          the preceding step.
  480. #
  481.  
  482. $stmt =
  483.       "
  484.        SELECT
  485.               owner
  486.             , index_name
  487.        FROM 
  488.               THE_INDEXES i
  489.        WHERE
  490.               NOT EXISTS   (
  491.                              SELECT
  492.                                     null
  493.                              FROM
  494.                                     THE_CONSTRAINTS
  495.                              WHERE
  496.                                         owner           = i.owner
  497.                                     AND constraint_name = i.index_name
  498.                            )
  499.               AND (
  500.                       owner
  501.                     , table_name
  502.                   ) NOT IN (
  503.                              SELECT
  504.                                     owner
  505.                                   , table_name
  506.                              FROM
  507.                                     THE_TABLES
  508.                              UNION ALL
  509.                              SELECT
  510.                                     owner
  511.                                   , table_name
  512.                              FROM
  513.                                     THE_IOTs
  514.                            )
  515.        ORDER
  516.           BY
  517.               1, 2
  518.       ";
  519.  
  520. $sth = $dbh->prepare( $stmt );
  521. $sth->execute;
  522. $aref = $sth->fetchall_arrayref;
  523.  
  524. $obj = DDL::Oracle->new(
  525.                          type => 'index',
  526.                          list => $aref,
  527.                        );
  528.  
  529. $drop_ddl .= group_header( 6 ) . $obj->drop    if @$aref;
  530.  
  531. #
  532. # Step 6 - Create ALL indexes.
  533. #
  534.  
  535. $stmt =
  536.       "
  537.        SELECT
  538.               owner
  539.             , index_name
  540.             , table_name
  541.             , analyzed
  542.        FROM 
  543.               THE_INDEXES
  544.        ORDER
  545.           BY
  546.               1, 2
  547.       ";
  548.  
  549. $sth = $dbh->prepare( $stmt );
  550. $sth->execute;
  551. $aref = $sth->fetchall_arrayref;
  552.  
  553. $obj = DDL::Oracle->new(
  554.                          type => 'index',
  555.                          list => $aref,
  556.                        );
  557.  
  558. $create_ndx_ddl .= group_header( 10 )    unless $header10++;
  559.  
  560. $create_ndx_ddl .= $obj->create    if @$aref;
  561.  
  562. foreach $row ( @$aref )
  563. {
  564.   my ( $owner, $index, $table, $analyzed ) = @$row;
  565.  
  566.   if ( $analyzed eq 'YES' )
  567.   {
  568.     $create_ndx_ddl .= "PROMPT " .
  569.                        "ANALYZE INDEX \L$owner.$index\n\n" .
  570.                        "ANALYZE INDEX \L$owner.$index\n" .
  571.                        "   ESTIMATE STATISTICS ;\n\n" .
  572.                        "PROMPT " .
  573.                        "ANALYZE TABLE \L$owner.$table\n\n" .
  574.                        "ANALYZE TABLE \L$owner.$table\n" .
  575.                        "   ESTIMATE STATISTICS " .
  576.                        "FOR ALL INDEXED COLUMNS ;\n\n";
  577.   }
  578. }
  579.  
  580. #
  581. # Step 7 - Create all Primary Key, Unique and Check constraints on our
  582. #          tables and on the tables of our indexes.  NOTE:  do not create
  583. #          the constraints for the IOT tables -- their primary keys were
  584. #          defined in the CREATE TABLE statements.
  585. #
  586.  
  587. $stmt =
  588.       "
  589.        SELECT
  590.               owner
  591.             , constraint_name
  592.             , constraint_type
  593.             , search_condition
  594.        FROM
  595.               dba_constraints
  596.        WHERE
  597.                   constraint_type     IN ('P','U','C')
  598.               AND (
  599.                       owner
  600.                     , table_name
  601.                   ) IN (
  602.                          SELECT
  603.                                 owner
  604.                               , table_name
  605.                          FROM
  606.                                 THE_TABLES
  607.                          UNION ALL
  608.                          SELECT
  609.                                 owner
  610.                               , table_name
  611.                          FROM
  612.                                 THE_INDEXES
  613.                        )
  614.        ORDER
  615.           BY
  616.               1, 2
  617.       ";
  618.  
  619. $dbh->{ LongReadLen } = 8192;    # Allows SEARCH_CONDITION length of 8K
  620. $dbh->{ LongTruncOk } = 1;
  621.  
  622. $sth = $dbh->prepare( $stmt );
  623. $sth->execute;
  624. $aref = $sth->fetchall_arrayref;
  625.  
  626. foreach $row ( @$aref )
  627. {
  628.   my ( $owner, $constraint_name, $cons_type, $condition, ) = @$row;
  629.  
  630.   if ( $cons_type ne 'C' )
  631.   {
  632.     push @constraints, [ $owner, $constraint_name ];
  633.   }
  634.   elsif ( $condition !~ /IS NOT NULL/ )  # NOT NULL is part of CREATE TABLE
  635.   {
  636.     push @constraints, [ $owner, $constraint_name ];
  637.   }
  638. }
  639.  
  640. $obj = DDL::Oracle->new(
  641.                          type => 'constraint',
  642.                          list => \@constraints,
  643.                        );
  644.  
  645. $create_ndx_ddl .= group_header( 11 ) . $obj->create    if @constraints;
  646.  
  647. #
  648. # Step 8 - Create all Check constraints on our IOT tables (their PK was
  649. #          part of the CREATE TABLE, and they can't have any other indexes,
  650. #          thus no UK's)
  651. #
  652.  
  653. $stmt =
  654.       "
  655.        SELECT
  656.               owner
  657.             , constraint_name
  658.             , constraint_type
  659.             , search_condition
  660.        FROM
  661.               dba_constraints
  662.        WHERE
  663.                   constraint_type = 'C'
  664.               AND (
  665.                       owner
  666.                     , table_name
  667.                   ) IN (
  668.                          SELECT
  669.                                 owner
  670.                               , table_name
  671.                          FROM
  672.                                 THE_IOTs
  673.                        )
  674.        ORDER
  675.           BY
  676.               1, 2
  677.       ";
  678.  
  679. $dbh->{ LongReadLen } = 8192;    # Allows SEARCH_CONDITION length of 8K
  680. $dbh->{ LongTruncOk } = 1;
  681.  
  682. $sth = $dbh->prepare( $stmt );
  683. $sth->execute;
  684. $aref = $sth->fetchall_arrayref;
  685.  
  686. @constraints = ();
  687. foreach $row ( @$aref )
  688. {
  689.   my ( $owner, $constraint_name, $cons_type, $condition, ) = @$row;
  690.  
  691.   if ( $condition !~ /IS NOT NULL/ )  # NOT NULL is part of CREATE TABLE
  692.   {
  693.     push @constraints, [ $owner, $constraint_name ];
  694.   }
  695. }
  696.  
  697. $obj = DDL::Oracle->new(
  698.                          type => 'constraint',
  699.                          list => \@constraints,
  700.                        );
  701.  
  702. $create_ndx_ddl .= group_header( 12 ) . $obj->create    if @constraints;
  703.  
  704. #
  705. # Step 9 - Recreate all Foreign Keys referenceing our tables and IOT's or
  706. #          referenceing the tables of our other indexes.  Use the same list
  707. #          used in Step 2 to drop them ($fk_aref).
  708. #
  709.  
  710. $obj = DDL::Oracle->new(
  711.                          type => 'constraint',
  712.                          list => $fk_aref,
  713.                        );
  714.  
  715. $create_ndx_ddl .= group_header( 13 ) . $obj->create    if @$fk_aref;
  716.  
  717. #
  718. # Step 10 - REBUILD all UNUSABLE indexes/index [sub]partitions. These are
  719. #           the non-partitioned or Global partitioned indexes on THE
  720. #           PARTITIONS.
  721. #
  722.  
  723. $stmt =
  724.       "
  725.        SELECT 
  726.               owner
  727.             , index_name
  728.        FROM
  729.               dba_indexes
  730.        WHERE
  731.               (
  732.                   owner
  733.                 , table_name
  734.               ) IN (
  735.                      SELECT
  736.                             owner
  737.                           , segment_name
  738.                      FROM
  739.                             THE_PARTITIONS
  740.                    )
  741.        MINUS
  742.        SELECT              -- Ignore partitioned, LOCAL indexes
  743.               owner
  744.             , index_name
  745.        FROM
  746.               dba_part_indexes
  747.        WHERE
  748.               locality = 'LOCAL'
  749.        ORDER
  750.           BY
  751.               1
  752.       ";
  753.  
  754. $sth = $dbh->prepare( $stmt );
  755. $sth->execute;
  756. $aref = $sth->fetchall_arrayref;
  757.  
  758. $obj = DDL::Oracle->new(
  759.                          type => 'index',
  760.                          list => $aref,
  761.                        );
  762.  
  763. $create_ndx_ddl .= group_header( 14 ) . $obj->resize    if @$aref;
  764.  
  765. #
  766. # It's hard to believe, but maybe they gave us an empty tablespace
  767. # to practice on.
  768. #
  769.  
  770. die "\n***Error:  Tablespace $tblsp is empty. 
  771.            Doest thou take me for a fool?\n\n"
  772.      unless $create_tbl_ddl . $create_ndx_ddl;
  773.  
  774. #
  775. # OK, we're ligit.  Coalesce all data/index tablespaces
  776. #
  777.  
  778. $stmt =
  779.       "
  780.        SELECT
  781.               LOWER(tablespace_name)
  782.        FROM
  783.               dba_tablespaces  t
  784.        WHERE
  785.                   status            = 'ONLINE'
  786.               AND contents         <> 'TEMPORARY'
  787.               AND tablespace_name  <> 'SYSTEM'
  788.               AND extent_management = 'DICTIONARY'
  789.        MINUS
  790.        SELECT
  791.               LOWER(tablespace_name)
  792.        FROM
  793.               dba_segments
  794.        WHERE
  795.               segment_type    = 'ROLLBACK'
  796.        ORDER
  797.           BY
  798.               1
  799.       ";
  800.  
  801. $sth = $dbh->prepare( $stmt );
  802. $sth->execute;
  803. $aref = $sth->fetchall_arrayref;
  804.  
  805. foreach $row ( @$aref )
  806. {
  807.   $drop_ddl .= "PROMPT ALTER TABLESPACE @$row->[0] COALESCE\n\n" .
  808.                "ALTER TABLESPACE @$row->[0] COALESCE ;\n\n",
  809. }
  810.  
  811. # Get rid of double blank lines
  812. $drop_ddl        =~ s|\n\n+|\n\n|g;
  813. $drop_temp_ddl   =~ s|\n\n+|\n\n|g;
  814. $create_tbl_ddl  =~ s|\n\n+|\n\n|g;
  815. $create_ndx_ddl  =~ s|\n\n+|\n\n|g;
  816. $create_temp_ddl =~ s|\n\n+|\n\n|g;
  817.  
  818. drop_perf_temps();
  819.  
  820. #
  821. # Wrap it up -- open, write and close all files
  822. #
  823.  
  824. if ( $create_temp_ddl )
  825. {
  826.   $add_temp_sql = "$sqldir/$prefix${tblsp}_add_temp.sql";
  827.   print "Create temps            : $add_temp_sql\n";
  828.   write_file( $add_temp_sql, $create_temp_ddl, 'REM' );
  829.  
  830.   $drop_temp_sql = "$sqldir/$prefix${tblsp}_drop_temp.sql";
  831.   print "Drop temps              : $drop_temp_sql\n";
  832.   write_file( $drop_temp_sql, $drop_temp_ddl, 'REM' );
  833. }
  834.  
  835. my $drop_all_sql = "$sqldir/$prefix${tblsp}_drop_all.sql";
  836. print "Drop objects            : $drop_all_sql\n";
  837. write_file( $drop_all_sql, $drop_ddl, 'REM' );
  838.  
  839. my $add_tbl_sql = "$sqldir/$prefix${tblsp}_add_tbl.sql";
  840. print "Create tables           : $add_tbl_sql\n";
  841. write_file( $add_tbl_sql, $create_tbl_ddl, 'REM' );
  842.  
  843. my $add_ndx_sql = "$sqldir/$prefix${tblsp}_add_ndx.sql";
  844. print "Create indexes          : $add_ndx_sql\n\n";
  845. write_file( $add_ndx_sql, $create_ndx_ddl, 'REM' );
  846.  
  847. my $pipefile = "$expdir/$prefix$tblsp.pipe";
  848. unlink $pipefile;
  849. eval { system ("mknod $pipefile p") };
  850.  
  851. if ( $create_temp_ddl )
  852. {
  853.   $prttn_exp_par   = "$expdir/$prefix${tblsp}_prttn_exp.par";
  854.   $prttn_exp_text  = export_par_text( $prttn_exp_log, \@export_temps);
  855.   print "Partition Export parfile: $prttn_exp_par\n";
  856.   print "Partition Export logfile: $prttn_exp_log\n";
  857.   write_file( $prttn_exp_par, $prttn_exp_text, '#' );
  858.  
  859.   $prttn_imp_par  = "$expdir/$prefix${tblsp}_prttn_imp.par";
  860.   $prttn_imp_text = import_par_text( $prttn_imp_log, \@export_temps );
  861.   print "Partition Import parfile: $prttn_imp_par\n";
  862.   print "Partition Import logfile: $prttn_imp_log\n\n";
  863.   write_file( $prttn_imp_par, $prttn_imp_text, '#' );
  864. }
  865.  
  866. my $exp_par   = "$expdir/$prefix${tblsp}_exp.par";
  867. my $exp_text  = export_par_text( $exp_log, \@export_objects );
  868. print "Table Export parfile    : $exp_par\n";
  869. print "Table Export logfile    : $exp_log\n";
  870. write_file( $exp_par, $exp_text, '#' );
  871.  
  872. my $imp_par  = "$expdir/$prefix${tblsp}_imp.par";
  873. my $imp_text = import_par_text( $imp_log, \@export_objects );
  874. print "Table Import parfile    : $imp_par\n";
  875. print "Table Import logfile    : $imp_log\n\n";
  876. write_file( $imp_par, $imp_text, '#' );
  877.  
  878. print "Export FIFO pipe        : $pipefile\n\n";
  879.  
  880. #
  881. # And, finally, the little shell scripts to help with the driving
  882. #
  883.  
  884. print "\n";
  885.  
  886. my $i     = 0;
  887. my $shell = "$sqldir/$prefix$tblsp.sh";
  888. my $gzip  = "$expdir/$prefix${tblsp}_prttn.dmp.gz";
  889.  
  890. if ( $create_temp_ddl )
  891. {
  892.  
  893.   $script = $shell . ++$i;
  894.   $text =
  895.     "# Step $i -- Export the partitions in Tablespace $tblsp\n\n" .
  896.     "nohup cat $pipefile | gzip -c \\\n" .
  897.     "        > $gzip &\n\n" .
  898.     "exp / parfile = $prttn_exp_par\n" .
  899.     check_exp_log( $script, $prttn_exp_log );
  900.   create_shell( $script, $text );
  901.  
  902.   $script = $shell . ++$i;
  903.   $text =
  904.     "# Step $i -- Use SQL*Plus to run $add_temp_sql\n" .
  905.     "#           which will create temp tables for partitions " .
  906.     "in tablespace $tblsp\n\n" .
  907.     "sqlplus -s / << EOF\n\n" .
  908.     "   SPOOL $add_temp_log\n\n" .
  909.     "   @ $add_temp_sql\n\n" .
  910.     "EOF\n" .
  911.     check_sql_log( $script, $add_temp_log );
  912.   create_shell( $script, $text );
  913. }
  914.  
  915. $script = $shell . ++$i;
  916. $text = "# Step $i -- Export the tables in Tablespace $tblsp\n\n";
  917. if ( @export_objects )
  918. {
  919.   $text .=
  920.   "nohup cat $pipefile | gzip -c \\\n" .
  921.   "        > $gzip &\n\n" .
  922.   "exp / parfile = $exp_par\n" .
  923.   check_exp_log( $script, $exp_log );
  924. }
  925. else
  926. {
  927.   $text .=
  928.   "echo\n" .
  929.   "echo There are no Tables in tablespace $tblsp.\n" .
  930.   "echo Skipping Export.\n" .
  931.   "echo\n" .
  932.   "echo $shell\n" .
  933.   "echo completed successfully without errors.\n" .
  934.   "echo on \` date \`\n" .
  935.   "echo\n\n";
  936. }
  937. create_shell( $script, $text );
  938.  
  939. $script = $shell . ++$i;
  940. $text =
  941.   "# Step $i -- Use SQL*Plus to run $drop_all_sql\n" .
  942.   "#           which will drop all objects in tablespace $tblsp\n\n" .
  943.   "sqlplus -s / << EOF\n\n" .
  944.   "   SPOOL $drop_all_log\n\n" .
  945.   "   @ $drop_all_sql\n\n" .
  946.   "EOF\n" .
  947.   check_sql_log( $script, $drop_all_log );
  948. create_shell( $script, $text );
  949.  
  950. $script = $shell . ++$i;
  951. $text =
  952.   "# Step $i -- Use SQL*Plus to run $add_tbl_sql\n".
  953.   "#           which will recreate all tables in tablespace $tblsp\n\n" .
  954.   "sqlplus -s / << EOF\n\n" .
  955.   "   SPOOL $add_tbl_log\n\n" .
  956.   "   @ $add_tbl_sql\n\n" .
  957.   "EOF\n" .
  958.   check_sql_log( $script, $add_tbl_log );
  959. create_shell( $script, $text );
  960.  
  961. $script = $shell . ++$i;
  962. $text = "# Step $i -- Import the tables back into Tablespace $tblsp\n\n";
  963. if ( @export_objects )
  964. {
  965.   $text .=
  966.   "nohup gunzip -c $gzip \\\n" .
  967.   "              > $pipefile &\n\n" .
  968.   "imp / parfile = $imp_par\n" .
  969.   check_imp_log( $script, $imp_log );
  970. }
  971. else
  972. {
  973.   $text .=
  974.   "echo\n" .
  975.   "echo There are no Tables in tablespace $tblsp.\n" .
  976.   "echo Skipping Import.\n" .
  977.   "echo\n" .
  978.   "echo $shell\n" .
  979.   "echo completed successfully without errors.\n" .
  980.   "echo on \` date \`\n" .
  981.   "echo\n\n";
  982. }
  983. create_shell( $script, $text );
  984.  
  985. $script = $shell . ++$i;
  986. $text =
  987.   "# Step $i -- Use SQL*Plus to run $add_ndx_sql\n" .
  988.   "#           which will recreate all indexes/constraints " .
  989.   "in tablespace $tblsp\n\n" .
  990.   "sqlplus -s / << EOF\n\n" .
  991.   "   SPOOL $add_ndx_log\n\n" .
  992.   "   @ $add_ndx_sql\n\n" .
  993.   "EOF\n" .
  994.   check_sql_log( $script, $add_ndx_log );
  995. create_shell( $script, $text );
  996.  
  997. $text = "echo $shell is being executed by $user\n" .
  998.         "echo on \` date \`\n\n";
  999.  
  1000. foreach my $j ( 1 .. $i )
  1001. {
  1002.   $text .= "$shell$j\n\n" .
  1003.            "RC=\$?\n\n" .
  1004.            "if [ \${RC} -gt 0 ]\n" .
  1005.            "then\n\n" .
  1006.            "   echo\n" .
  1007.            "   echo\n" .
  1008.            "   echo '*** ERROR'\n" .
  1009.            "   echo $shell$j failed\n" .
  1010.            "   echo on \` date \`\n" .
  1011.            "   echo\n" .
  1012.            "   exit \${RC}\n\n" .
  1013.            "fi\n\n";
  1014. }
  1015.  
  1016. $text .= "echo And so did $shell\n" .
  1017.          "echo\n" .
  1018.          "echo YAHOO!!\n" .
  1019.          "echo\n" .
  1020.          "exit 0\n\n";
  1021.  
  1022. print "\nAnd if you want a driver script for all of the above, it is:\n\n",
  1023.       "   $shell\n\n\n";
  1024. open SHELL, ">$shell"     or die "Can't open $shell: $!\n";
  1025. write_header( \*SHELL, $shell, '# ' );
  1026. print SHELL $text . "#  --- END OF FILE ---\n\n";
  1027. close SHELL                  or die "Can't close $shell: $!\n";
  1028.  
  1029. if ( $create_temp_ddl )
  1030. {
  1031.   $gzip  = "$expdir/$prefix${tblsp}_prttn.dmp.gz";
  1032.  
  1033.   print "\n*** The following 2 scripts ARE FOR FALLBACK PURPOSES ONLY!!\n" .
  1034.         "*** Use these scripts ONLY IF Shell #2 HAD ERRORS.\n\n";
  1035.  
  1036.   $script = $shell . ++$i;
  1037.   $text =
  1038.     "# USE FOR FALLBACK PURPOSES ONLY\n\n" .
  1039.     "# Use SQL*Plus to run $drop_temp_sql\n" .
  1040.     "# which will drop the temp tables holding data for partitions " .
  1041.     "in tablespace $tblsp\n\n" .
  1042.     "sqlplus -s / << EOF\n\n" .
  1043.     "   SPOOL $drop_temp_log\n\n" .
  1044.     "   @ $drop_temp_sql\n\n" .
  1045.     "EOF\n" .
  1046.     check_sql_log( $script, $drop_temp_log );
  1047.   create_shell( $script, $text );
  1048.  
  1049.   $script = $shell . ++$i;
  1050.   $text =
  1051.     "# USE FOR FALLBACK PURPOSES ONLY\n\n" .
  1052.     "#Import the tables back into the partitions in " .
  1053.     "Tablespace $tblsp\n\n" .
  1054.     "echo\n" .
  1055.     "echo \"**************** NOTICE ***************\"\n" .
  1056.     "echo\n" .
  1057.     "echo Ignore warnings about missing partitions -- because not\n" .
  1058.     "echo all partitions were exported, and thus not all partitions\n" .
  1059.     "echo need be re-imported.\n" .
  1060.     "echo The error to be ignored is:\n" .
  1061.     "echo\n" .
  1062.     "echo \"  IMP-00057: Warning: Dump file may not contain data of all partitions...\"\n" .
  1063.     "echo\n" .
  1064.     "echo \"************ END OF NOTICE ************\"\n\n" .
  1065.     "nohup gunzip -c $gzip \\\n" .
  1066.     "              > $pipefile &\n\n" .
  1067.     "imp / parfile = $prttn_imp_par\n" .
  1068.     check_imp_log( $script, $prttn_imp_log );
  1069.   create_shell( $script, $text );
  1070. }
  1071.  
  1072. my @shells = glob( "$sqldir/$prefix$tblsp.sh*" );
  1073. chmod( 0754, @shells ) == @shells or die "\nCan't chmod some shells: $!\n";
  1074.  
  1075. print "\n$0 completed successfully\non ", scalar localtime,"\n\n";
  1076.  
  1077. exit 0;
  1078.  
  1079. #################### Subroutines (alphabetically) ######################
  1080.  
  1081. # sub check
  1082. #
  1083. # returns text for a shell script to check its LOG file for errors
  1084. #
  1085. sub check
  1086. {
  1087.   my ($shell, $log ) = @_;
  1088.  
  1089.   return
  1090. "then
  1091.  
  1092.    echo
  1093.    echo '*** ERRORS during'
  1094.    echo $shell
  1095.    echo
  1096.    echo CHECK LOG $log
  1097.    echo
  1098.    exit 1
  1099.  
  1100. else
  1101.  
  1102.    echo
  1103.    echo $shell
  1104.    echo completed successfully without errors.
  1105.    echo on \` date \`
  1106.    echo
  1107.  
  1108. fi
  1109.  
  1110. ";
  1111.  
  1112. }
  1113.  
  1114. # sub check_exp_log
  1115. #
  1116. # returns text for a shell script to check its exp log file for errors
  1117. #
  1118. sub check_exp_log
  1119. {
  1120.   my ( $shell, $log ) = @_;
  1121.  
  1122.   return
  1123. "
  1124. cat $log 
  1125.  
  1126. EXP=\` grep -c ^EXP- $log \`
  1127. ORA=\` grep -c ^ORA- $log \`
  1128.  
  1129. if [ \${ORA} -gt 0 -o \${EXP} -gt 0 ]
  1130. " . 
  1131. check( @_ );
  1132. }
  1133.  
  1134. # sub check_imp_log
  1135. #
  1136. # returns text for a shell script to check its imp log file for errors
  1137. #
  1138. sub check_imp_log
  1139. {
  1140.   my ( $shell, $log ) = @_;
  1141.  
  1142.   # Check log for errors, but ignore:
  1143.   #   IMP-00057 -- Not all partitions imported (we didn't export them all)
  1144.   #   IMP-00041 -- Store PL/SQL compilation errors (not our fault)
  1145.   return
  1146. "
  1147. cat $log 
  1148.  
  1149. IMP=\` grep -v ^IMP-00057 $log | \\
  1150.       grep -v ^IMP-00041 | \\
  1151.       grep -c ^IMP- \`
  1152. ORA=\` grep -c ^ORA- $log \`
  1153.  
  1154. if [ \${ORA} -gt 0 -o \${IMP} -gt 0 ]
  1155. " . 
  1156. check( @_ );
  1157. }
  1158.  
  1159. # sub check_sql_log
  1160. #
  1161. # returns text for a shell script to check its SQL spool file for errors
  1162. #
  1163. sub check_sql_log
  1164. {
  1165.   my ( $shell, $log ) = @_;
  1166.  
  1167.   return
  1168. "
  1169. ORA=\` grep -c ^ORA- $log \`
  1170.  
  1171. if [ \${ORA} -gt 0 ]
  1172. " . 
  1173. check( @_ );
  1174.  
  1175. }
  1176.  
  1177. # sub connect_to_oracle
  1178. #
  1179. # Requires both "user" and "password", or neither.  If "user" is supplied
  1180. # but not "password", will prompt for a "password".  On Unix systems, a
  1181. # system call to "stty" is made before- and after-hand to control echoing
  1182. # of keystrokes.  [How do we do this on Windows?]
  1183. #
  1184. sub connect_to_oracle
  1185. {
  1186.   if ( $args{ user } and not $args{ password } )
  1187.   {
  1188.     print "Enter password: ";
  1189.     eval{ system("stty -echo" ); };
  1190.     chomp( $args{ password } = <STDIN> );
  1191.     print "\n";
  1192.     eval{ system( "stty echo" ); };
  1193.   }
  1194.  
  1195.   $args{ sid }      = "" unless $args{ sid };
  1196.   $args{ user }     = "" unless $args{ user };
  1197.   $args{ password } = "" unless $args{ password };
  1198.  
  1199.   $dbh = DBI->connect(
  1200.                        "dbi:Oracle:$args{ sid }",
  1201.                        "$args{ user }",
  1202.                        "$args{ password }",
  1203.                        {
  1204.                          PrintError => 0,
  1205.                          RaiseError => 1,
  1206.                        }
  1207.                      );
  1208.  
  1209.   # $dbh->do( "alter session set sql_trace = true" );
  1210.  
  1211.   DDL::Oracle->configure(
  1212.                           dbh    => $dbh,
  1213.                           view   => 'DBA',
  1214.                           schema => 1,
  1215.                           resize => $args{ resize } || 1,
  1216.                         );
  1217. }
  1218.  
  1219. # sub create_shell
  1220. #
  1221. # Opens, writes $text, closes the named shell script
  1222. #
  1223. sub create_shell
  1224. {
  1225.   my ( $script, $text ) = @_;
  1226.  
  1227.   print "Shell #$i is $script\n";
  1228.   open SHELL, ">$script"     or die "Can't open $script: $!\n";
  1229.   write_header( \*SHELL, $script, '# ' );
  1230.   print SHELL $text . "#  --- END OF FILE ---\n\n";
  1231.   close SHELL                  or die "Can't close $script: $!\n";
  1232. }
  1233.  
  1234. # sub drop_perf_temps
  1235. #
  1236. # Drops the temporary tables created to boost performance
  1237. #
  1238. sub drop_perf_temps
  1239. {
  1240.   foreach my $table ( @perf_tables )
  1241.   {
  1242.     $stmt =
  1243.      "
  1244.       SELECT
  1245.              'Yo!'
  1246.       FROM
  1247.              user_synonyms
  1248.       WHERE
  1249.              synonym_name = UPPER( ? )
  1250.      ";
  1251.  
  1252.     $sth = $dbh->prepare( $stmt );
  1253.     $sth->execute( $table );
  1254.     my $present = $sth->fetchrow_array;
  1255.     $dbh->do( "DROP SYNONYM $table" )    if $present;
  1256.  
  1257.     if ( $table =~ /^DBA/ )
  1258.     {
  1259.       $stmt =
  1260.        "
  1261.         SELECT
  1262.                'Present, sir!'
  1263.         FROM
  1264.                user_tables
  1265.         WHERE
  1266.                table_name = UPPER( ? )
  1267.        ";
  1268.  
  1269.       $sth = $dbh->prepare( $stmt );
  1270.       $sth->execute( "$prefix$table" );
  1271.       my $present = $sth->fetchrow_array;
  1272.  
  1273.       if ( $present )
  1274.       {
  1275.         $dbh->do( "TRUNCATE TABLE $prefix$table" );
  1276.         $dbh->do( "DROP     TABLE $prefix$table" );
  1277.       }
  1278.     }
  1279.     else
  1280.     {
  1281.       $stmt =
  1282.        "
  1283.         SELECT
  1284.                'Present, sir!'
  1285.         FROM
  1286.                user_tables
  1287.         WHERE
  1288.                table_name = ?
  1289.        ";
  1290.  
  1291.       $sth = $dbh->prepare( $stmt );
  1292.       $sth->execute( $table );
  1293.       my $present = $sth->fetchrow_array;
  1294.  
  1295.       if ( $present )
  1296.       {
  1297.         $dbh->do( "TRUNCATE TABLE $table" );
  1298.         $dbh->do( "DROP     TABLE $table" );
  1299.       }
  1300.     }
  1301.   }
  1302. }
  1303.  
  1304. # sub escaped_dollar_signs
  1305. #
  1306. # Routines dealing with the Temp tables, indexes and constraints must
  1307. # substitute generated names for the names of real objects returned by
  1308. # DDL::Oracle.  However, Oracle allows dollar signs ('$') within names
  1309. # for database objects.  This causes problems with the s/// operator,
  1310. # since it sees the '$' as a meta character, causing the substitution
  1311. # to fail.
  1312. #
  1313. # This little subroutine inserts a '\' in front of each '$', which
  1314. # effectively escapes it for the s/// operator.
  1315. #
  1316. sub escaped_dollar_signs
  1317. {
  1318.   my ( $str ) = @_;
  1319.  
  1320.   my $pos = 0;
  1321.  
  1322.   until ( $pos == -1 )
  1323.   {
  1324.     $pos = index( $str, '$', $pos );
  1325.     if ( $pos > -1 )
  1326.     {
  1327.       substr( $str, $pos, 0 ) = qq#\\#;
  1328.       $pos += 2;
  1329.     }
  1330.   }
  1331.  
  1332.   return $str;
  1333. }
  1334.  
  1335. # sub export_par_text
  1336. #
  1337. # Returns the text for the parfile of an export
  1338. #
  1339. sub export_par_text
  1340. {
  1341.   my ( $log, $table_aref ) = @_;
  1342.  
  1343.   my $text = "log          = $log\n" .
  1344.              "file         = $pipefile\n" .
  1345.              "rows         = y\n" .
  1346.              "grants       = y\n";
  1347.  
  1348.   # My linux Oracle 8.1.6 has a bug, so
  1349.   $text   .= "direct       = y\n"    unless $OSNAME eq 'linux';
  1350.  
  1351.   $text   .= "buffer       = 65535\n" .
  1352.              "indexes      = n\n" .
  1353.              "compress     = n\n" .
  1354.              "triggers     = y\n" .
  1355.              "statistics   = none\n" .
  1356.              "constraints  = n\n" .
  1357.              "recordlength = 65535\n" .
  1358.              "tables       = (\n" .
  1359.              "                   " .
  1360.              join ( "\n                 , ", @$table_aref ) .
  1361.              "\n               )\n\n";
  1362.  
  1363.   return $text
  1364. }
  1365.  
  1366. # sub get_args
  1367. #
  1368. # Uses supplied module Getopt::Long to place command line options into the
  1369. # hash %args.  Ensures that at least the mandatory argument --tablespace
  1370. # was supplied.  Also verifies directory arguments and connects to Oracle.
  1371. #
  1372. sub get_args
  1373. {
  1374.   #
  1375.   # Get options from command line and store in %args
  1376.   #
  1377.   GetOptions(
  1378.               \%args,
  1379.               "alttablespace:s",
  1380.               "expdir:s",
  1381.               "logdir:s",
  1382.               "password:s",
  1383.               "prefix:s",
  1384.               "sid:s",
  1385.               "resize:s",
  1386.               "sqldir:s",
  1387.               "tablespace:s",
  1388.               "user:s",
  1389.             );
  1390.  
  1391.   #
  1392.   # If there is anything left in @ARGV, we have a problem
  1393.   #
  1394.   die "\n***Error:  unrecognized argument",
  1395.       ( @ARGV == 1 ? ":  " : "s:  " ),
  1396.       ( join " ",@ARGV ),
  1397.       "\n$0 aborted,\n\n" ,
  1398.     if @ARGV;
  1399.   
  1400.   #
  1401.   # Validate arguments (maybe they type as badly as we do!
  1402.   #
  1403.  
  1404.   $tblsp = uc( $args{ tablespace } ) or
  1405.   die "\n***Error:  You must specify --tablespace=<NAME>\n",
  1406.       "\n$0 aborted,\n\n";
  1407.  
  1408.   $sqldir = ( $args{ sqldir } eq "." ) ? cwd : $args{ sqldir };
  1409.   die "\n***Error:  sqldir '$sqldir', is not a Directory\n",
  1410.       "\n$0 aborted,\n\n"
  1411.     unless -d $sqldir;
  1412.  
  1413.   die "\n***Error:  sqldir '$sqldir', is not a writeable Directory\n",
  1414.       "\n$0 aborted,\n\n"
  1415.     unless -w $sqldir;
  1416.  
  1417.   $logdir = ( $args{ logdir } eq "." ) ? cwd : $args{ logdir };
  1418.   die "\n***Error:  logdir '$logdir', is not a Directory\n",
  1419.       "\n$0 aborted,\n\n"
  1420.     unless -d $logdir;
  1421.  
  1422.   die "\n***Error:  logdir '$logdir', is not a writeable Directory\n",
  1423.       "\n$0 aborted,\n\n"
  1424.     unless -w $logdir;
  1425.  
  1426.   $expdir = ( $args{ expdir } eq "." ) ? cwd : $args{ expdir };
  1427.   die "\n***Error:  expdir '$expdir', is not a Directory\n",
  1428.       "\n$0 aborted,\n\n"
  1429.     unless -d $expdir;
  1430.  
  1431.   die "\n***Error:  sqldir '$expdir', is not a writeable Directory\n",
  1432.       "\n$0 aborted,\n\n"
  1433.     unless -w $expdir;
  1434.  
  1435.   $prefix = $args{ prefix };
  1436.  
  1437.   $add_ndx_log     = "$logdir/$prefix${tblsp}_add_ndx.log";
  1438.   $add_tbl_log     = "$logdir/$prefix${tblsp}_add_tbl.log";
  1439.   $add_temp_log    = "$logdir/$prefix${tblsp}_add_temp.log";
  1440.   $drop_all_log    = "$logdir/$prefix${tblsp}_drop_all.log";
  1441.   $drop_temp_log   = "$logdir/$prefix${tblsp}_drop_temp.log";
  1442.   $exp_log         = "$logdir/$prefix${tblsp}_exp.log";
  1443.   $imp_log         = "$logdir/$prefix${tblsp}_imp.log";
  1444.   $prttn_exp_log   = "$logdir/$prefix${tblsp}_prttn_exp.log";
  1445.   $prttn_imp_log   = "$logdir/$prefix${tblsp}_prttn_imp.log";
  1446.  
  1447.   push @logfiles, (
  1448.                     $add_ndx_log,
  1449.                     $add_tbl_log,
  1450.                     $add_temp_log,
  1451.                     $drop_all_log,
  1452.                     $drop_temp_log,
  1453.                     $exp_log,
  1454.                     $imp_log,
  1455.                     $prttn_exp_log,
  1456.                     $prttn_imp_log,
  1457.                   );
  1458.  
  1459.   validate_log_names( \@logfiles );
  1460.  
  1461.   $alttblsp = uc( $args{ alttablespace } );
  1462.  
  1463.   connect_to_oracle();      # Will fail unless sid, user, password are OK
  1464.  
  1465.   print "Initializing private copies of some dictionary views...\n\n";
  1466.  
  1467.   initialize_perf_temps();
  1468.  
  1469.   # Confirm the tablespace exists
  1470.   $stmt =
  1471.       "
  1472.        SELECT
  1473.               tablespace_name
  1474.        FROM
  1475.               dba_tablespaces  t
  1476.        WHERE
  1477.                   tablespace_name   = '$tblsp'
  1478.               AND status            = 'ONLINE'
  1479.               AND contents         <> 'TEMPORARY'
  1480.               AND extent_management = 'DICTIONARY'
  1481.        MINUS
  1482.        SELECT
  1483.               tablespace_name
  1484.        FROM
  1485.               dba_segments
  1486.        WHERE
  1487.               segment_type = 'ROLLBACK'
  1488.       ";
  1489.  
  1490.   $sth = $dbh->prepare( $stmt );
  1491.   $sth->execute;
  1492.   $row = $sth->fetchrow_array;
  1493.  
  1494.   die "\n***Error:  Tablespace \U$tblsp",
  1495.       " does not exist\n",
  1496.       "           or is not ONLINE\n",
  1497.       "           or is managed LOCALLY\n",
  1498.       "           or is a TEMPORARY tablespace\n",
  1499.       "           or contains ROLLBACK segments.\n\n"
  1500.     unless $row;
  1501.  
  1502.   # First row returned is valid tablespace, and is $alttblsp.
  1503.   # Since we know $tblsp is good, we're guaranteed at least one row.
  1504.   $stmt =
  1505.       "
  1506.        (
  1507.          SELECT
  1508.                 tablespace_name
  1509.          FROM
  1510.                 dba_tablespaces
  1511.          WHERE
  1512.                     tablespace_name   = '$alttblsp'
  1513.                 AND status            = 'ONLINE'
  1514.                 AND contents         <> 'TEMPORARY'
  1515.                 AND extent_management = 'DICTIONARY'
  1516.          MINUS
  1517.          SELECT
  1518.                 tablespace_name
  1519.          FROM
  1520.                 dba_segments
  1521.          WHERE
  1522.                 segment_type = 'ROLLBACK'
  1523.        )
  1524.        UNION ALL
  1525.        (
  1526.          SELECT
  1527.                 tablespace_name
  1528.          FROM
  1529.                 dba_tablespaces
  1530.          WHERE
  1531.                     tablespace_name   = 'USERS'
  1532.                 AND status            = 'ONLINE'
  1533.                 AND contents         <> 'TEMPORARY'
  1534.                 AND extent_management = 'DICTIONARY'
  1535.          MINUS
  1536.          SELECT
  1537.                 tablespace_name
  1538.          FROM
  1539.                 dba_segments
  1540.          WHERE
  1541.                 segment_type = 'ROLLBACK'
  1542.        )
  1543.        UNION ALL
  1544.        (
  1545.          SELECT
  1546.                 '$tblsp'
  1547.          FROM
  1548.                 dual
  1549.        )
  1550.       ";
  1551.  
  1552.   $sth = $dbh->prepare( $stmt );
  1553.   $sth->execute;
  1554.   $aref = $sth->fetchall_arrayref;
  1555.  
  1556.   $alttblsp = ( shift @$aref )->[0];
  1557.  
  1558.   my ( undef,undef,undef,$day,$month,$year,undef,undef,undef ) = localtime;
  1559.   $date = $year + 1900 . $month + 1 . $day;
  1560. }
  1561.  
  1562. # sub group_header
  1563. #
  1564. # Returns a Remark to identify the ensuing DDL statements
  1565. #
  1566. sub group_header
  1567. {
  1568.   my ( $nbr ) = @_;
  1569.  
  1570.   return 'REM ' . '#' x 60 . "\n" .
  1571.          "REM\n" .
  1572.          "REM                      Statement Group $nbr\n" .
  1573.          "REM\n" .
  1574.          'REM ' . '#' x 60 . "\n\n";
  1575. }
  1576.  
  1577. # sub import_par_text
  1578. #
  1579. # Returns the text for the parfile of an import
  1580. #
  1581. sub import_par_text
  1582. {
  1583.   my ( $log, $table_aref ) = @_;
  1584.  
  1585.   return            "log          = $log\n" .
  1586.                     "file         = $pipefile\n" .
  1587.                     "rows         = y\n" .
  1588.                     "commit       = y\n" .
  1589.                     "ignore       = y\n" .
  1590.                     "buffer       = 65535\n" .
  1591.                     "analyze      = n\n" .
  1592.                     "recordlength = 65535\n" .
  1593.                     "full         = y\n\n" .
  1594.                     "#tables       = (\n" .
  1595.                     "#                   " .
  1596.                     join ( "\n#                 , ", @$table_aref ) .
  1597.                     "\n#               )\n\n";
  1598. }
  1599.  
  1600. # sub index_and_exchange
  1601. #
  1602. # Generate the DDL to:
  1603. #
  1604. # 1.  Create an index on named temp table equal to every LOCAL index on the
  1605. #     named partitioned table.
  1606. # 2.  Create a PK for the temp table equal to the PK of the partitioned table,
  1607. #     if any.
  1608. # 3.  Exchange the temp table with the named partition.
  1609. #
  1610. sub index_and_exchange
  1611. {
  1612.   my ( 
  1613.        $temp,
  1614.        $owner, 
  1615.        $table, 
  1616.        $partition, 
  1617.        $type, 
  1618.        $partitioning_type,
  1619.        $analyzed
  1620.      ) = @_;
  1621.  
  1622.   my $sql;
  1623.   my $text;
  1624.  
  1625.   # Get partitioned, local indexes
  1626.   $stmt =
  1627.       "
  1628.        SELECT DISTINCT
  1629.               index_name
  1630.        FROM
  1631.               dba_indexes
  1632.        WHERE
  1633.                   owner      = ?
  1634.               AND table_name = ?
  1635.        MINUS
  1636.        SELECT                     -- Ignore GLOBAL indexes
  1637.               index_name
  1638.        FROM
  1639.               dba_part_indexes
  1640.        WHERE
  1641.                   owner      = ?
  1642.               AND table_name = ?
  1643.               AND locality   = 'GLOBAL'
  1644.        MINUS
  1645.        SELECT                     -- Ignore non-partitioned indexes
  1646.               segment_name
  1647.        FROM
  1648.               dba_segments
  1649.        WHERE
  1650.               segment_type = 'INDEX'
  1651.        ORDER
  1652.           BY
  1653.               1
  1654.       ";
  1655.  
  1656.   $sth = $dbh->prepare( $stmt );
  1657.   $sth->execute( $owner, $table, $owner, $table );
  1658.   $aref = $sth->fetchall_arrayref;
  1659.  
  1660.   foreach $row ( @$aref )
  1661.   {
  1662.     my $index = @$row->[0];
  1663.  
  1664.     $obj = DDL::Oracle->new(
  1665.                              type => 'exchange index',
  1666.                              list => [
  1667.                                        [
  1668.                                          "$owner",
  1669.                                          "$index:$partition",
  1670.                                        ]
  1671.                                      ],
  1672.                            );
  1673.     my $sql = $obj->create;
  1674.     # Remove REM lines created by DDL::Oracle
  1675.     $sql =  ( join "\n", grep !/^REM/, split /\n/, $sql ) . "\n\n";
  1676.  
  1677.     my $indx =  "${tblsp}_${date}_" . unique_nbr();
  1678.  
  1679.     # Change the CREATE INDEX statement
  1680.     # to use the Temp Index and Table names
  1681.     my $ownr = escaped_dollar_signs( $owner );
  1682.     my $tabl = escaped_dollar_signs( $table );
  1683.     my $indr = escaped_dollar_signs( $index );
  1684.     $sql     =~ s|\L$ownr.$indr|\L$owner.$indx|g;
  1685.     $sql     =~ s|\L$ownr.$tabl|\L$owner.$temp|g;
  1686.  
  1687.     $text .= $sql;
  1688.   }
  1689.  
  1690.   $stmt =
  1691.       "
  1692.        SELECT
  1693.               constraint_name
  1694.        FROM
  1695.               THE_CONSTRAINTS
  1696.        WHERE
  1697.                   owner           = ?
  1698.               AND table_name      = ?
  1699.               AND constraint_type = 'P'
  1700.       ";
  1701.  
  1702.   $sth = $dbh->prepare( $stmt );
  1703.   $sth->execute( $owner, $table );
  1704.   my @row = $sth->fetchrow_array;
  1705.  
  1706.   if ( @row )
  1707.   {
  1708.     my ( $constraint ) = @row;
  1709.  
  1710.     $obj = DDL::Oracle->new(
  1711.                              type => 'constraint',
  1712.                              list => [
  1713.                                        [
  1714.                                          "$owner",
  1715.                                          "$constraint",
  1716.                                        ]
  1717.                                      ],
  1718.                            );
  1719.     my $sql = $obj->create;
  1720.     # Remove REM lines created by DDL::Oracle
  1721.     $sql =  ( join "\n", grep !/^REM/, split /\n/, $sql ) . "\n\n";
  1722.  
  1723.     my $cons =  "${tblsp}_${date}_" . unique_nbr();
  1724.  
  1725.     # Change the ALTER TABLE ADD CONSTRAINT statement
  1726.     # to use the Temp Constraint and Table names
  1727.     my $ownr = escaped_dollar_signs( $owner );
  1728.     my $tabl = escaped_dollar_signs( $table );
  1729.     my $conr = escaped_dollar_signs( $constraint );
  1730.     $sql     =~ s|\L$ownr.$tabl|\L$owner.$temp|g;
  1731.     $sql     =~ s|\L$conr|\L$cons|g;
  1732.  
  1733.     $text .= $sql;
  1734.   }
  1735.  
  1736.   if ( $analyzed eq 'YES' )
  1737.   {
  1738.     $text .= "PROMPT " .
  1739.              "ANALYZE TABLE \L$owner.$temp\n\n" .
  1740.              "ANALYZE TABLE \L$owner.$temp \UESTIMATE STATISTICS\n" .
  1741.              "   FOR TABLE\n" .
  1742.              "   FOR ALL INDEXED COLUMNS ;\n\n";
  1743.   }
  1744.  
  1745.   $text .= "PROMPT " .
  1746.            "ALTER TABLE \L$owner.$table \UEXCHANGE $type \L$partition\n\n" .
  1747.            "ALTER TABLE \L$owner.$table\n" .
  1748.            "   \UEXCHANGE $type \L$partition \UWITH TABLE \L$owner.$temp\n" .
  1749.            "   INCLUDING INDEXES\n".
  1750.            "   WITHOUT VALIDATION ;\n\n";
  1751.  
  1752.   return $text;
  1753. }
  1754.  
  1755. # sub initialize_perf_temps
  1756. #
  1757. sub initialize_perf_temps
  1758. {
  1759.   # Drop the Performance enhancing tables -- they shouldn't be here,
  1760.   # but who knows, maybe we crashed last time (how rude!)
  1761.  
  1762.   drop_perf_temps();
  1763.  
  1764.   # Some Dictionary views are queried repeatedly by us (defrag.pl) as well
  1765.   # as by DDL::Oracle.  They are often complex views, taking as much as 3
  1766.   # to 10 seconds for each query on a large database (e.g., 50,000 segments).
  1767.   # Let's get our own, more efficient copy of this data and avoid this
  1768.   # overhead
  1769.  
  1770.   TABLE:
  1771.     foreach my $table ( @perf_tables )
  1772.     {
  1773.       next TABLE unless $table =~ /^DBA/;
  1774.  
  1775.       $dbh->do
  1776.       ( "
  1777.          CREATE GLOBAL TEMPORARY TABLE $prefix$table
  1778.          ON COMMIT PRESERVE ROWS
  1779.          AS
  1780.          SELECT
  1781.                 *
  1782.          FROM
  1783.                 sys.$table
  1784.         "
  1785.       );
  1786.       $dbh->do( "CREATE SYNONYM $table FOR $prefix$table" );
  1787.     }
  1788. }
  1789.  
  1790. # sub initialize_queries
  1791. #
  1792. # Initializes the driving queries used to retrieve object names involved in
  1793. # the defrag.  Because these are UNIONed and MINUSed, at times, store the
  1794. # the results in in-memory temporary tables for efficiency reasons.
  1795. #
  1796. sub initialize_queries
  1797. {
  1798.   # This query produces a list of THE CONSTRAINTS, sans search_condition
  1799.   # which is needed for creating Check Constraints
  1800.   $stmt =
  1801.       "
  1802.        CREATE GLOBAL TEMPORARY TABLE the_constraints
  1803.        ON COMMIT PRESERVE ROWS
  1804.        AS
  1805.        SELECT
  1806.               owner
  1807.             , constraint_name
  1808.             , constraint_type
  1809.             , table_name
  1810.             , r_owner
  1811.             , r_constraint_name
  1812.        FROM
  1813.               dba_constraints
  1814.       ";
  1815.  
  1816.   $dbh->do( $stmt );
  1817.  
  1818.   # This query produces a list of THE PARTITIONS, which are the partitions
  1819.   # in THE TABLESPACE belonging to tables which have at least one partition
  1820.   # in some other tablespace.  These will be the target of ALTER TABLE
  1821.   # EXCHANGE [SUB]PARTITION statements with "temp" tables.
  1822.   #
  1823.   $stmt =
  1824.       "
  1825.        CREATE GLOBAL TEMPORARY TABLE the_partitions
  1826.        ON COMMIT PRESERVE ROWS
  1827.        AS
  1828.        SELECT
  1829.               s.owner
  1830.             , s.segment_name
  1831.             , s.partition_name
  1832.             , SUBSTR(s.segment_type,7)                 AS segment_type
  1833.             , p.partitioning_type                      AS partitioning_type
  1834.             , DECODE(
  1835.                       s.segment_type
  1836.                      ,'TABLE PARTITION'   ,DECODE(
  1837.                                                    a.last_analyzed
  1838.                                                   ,null,'NO'
  1839.                                                   ,'YES'
  1840.                                                  )
  1841.                      ,'TABLE SUBPARTITION',DECODE(
  1842.                                                    b.last_analyzed
  1843.                                                   ,null,'NO'
  1844.                                                   ,'YES'
  1845.                                                  )
  1846.                     )                                  AS analyzed
  1847.        FROM
  1848.               dba_segments          s
  1849.             , dba_part_tables       p
  1850.             , dba_tab_partitions    a
  1851.             , dba_tab_subpartitions b
  1852.        WHERE
  1853.                   p.table_name            = s.segment_name
  1854.               AND s.segment_type       LIKE 'TABLE%PARTITION'
  1855.               AND s.tablespace_name       = '$tblsp'
  1856.               AND a.table_name        (+) = s.segment_name
  1857.               AND b.table_name        (+) = s.segment_name
  1858.               AND a.partition_name    (+) = s.partition_name
  1859.               AND b.subpartition_name (+) = s.partition_name
  1860.               AND a.table_owner       (+) = s.owner
  1861.               AND b.table_owner       (+) = s.owner
  1862.               AND EXISTS (
  1863.                            SELECT
  1864.                                   null
  1865.                            FROM
  1866.                                   dba_segments
  1867.                            WHERE
  1868.                                       segment_type  LIKE 'TABLE%PARTITION'
  1869.                                   AND tablespace_name <> '$tblsp'
  1870.                                   AND owner            = s.owner
  1871.                                   AND segment_name     = s.segment_name
  1872.                          )
  1873.               AND (
  1874.                       s.owner
  1875.                     , s.segment_name
  1876.                   ) NOT IN (
  1877.                              SELECT
  1878.                                     owner
  1879.                                   , table_name
  1880.                              FROM
  1881.                                     dba_snapshots
  1882.                            )
  1883.       ";
  1884.  
  1885.   $dbh->do( $stmt );
  1886.  
  1887.   # This query produces a list of THE INDEXES (and their tables) -- those
  1888.   # non-partitioned indexes which reside in THE TABLESPACE, plus indexes 
  1889.   # which have at least one partition in THE TABLESPACE.  These indexes are
  1890.   # on tables other than the tables of THE PARTITIONS but may be on THE
  1891.   # TABLES.
  1892.   #
  1893.   $stmt =
  1894.       "
  1895.        CREATE GLOBAL TEMPORARY TABLE the_indexes
  1896.        ON COMMIT PRESERVE ROWS
  1897.        AS
  1898.        SELECT
  1899.               owner
  1900.             , index_name
  1901.             , table_name
  1902.             , MAX(analyzed)        AS analyzed
  1903.        FROM
  1904.             (
  1905.               SELECT
  1906.                      owner
  1907.                    , index_name
  1908.                    , table_name
  1909.                    , DECODE(
  1910.                              last_analyzed
  1911.                             ,null,'NO'
  1912.                             ,'YES'
  1913.                            )                  AS analyzed
  1914.               FROM
  1915.                      dba_indexes
  1916.               WHERE
  1917.                          tablespace_name = '$tblsp'
  1918.                      AND index_type     <> 'IOT - TOP'
  1919.               UNION ALL
  1920.               SELECT
  1921.                      i.owner
  1922.                    , i.index_name
  1923.                    , i.table_name
  1924.                    , DECODE(
  1925.                              p.last_analyzed
  1926.                             ,null,'NO'
  1927.                             ,'YES'
  1928.                            )                  AS analyzed
  1929.               FROM
  1930.                      dba_indexes         i
  1931.                    , dba_ind_partitions  p
  1932.               WHERE
  1933.                          p.tablespace_name = '$tblsp'
  1934.                      AND i.owner           = p.index_owner
  1935.                      AND i.index_name      = p.index_name
  1936.                      AND i.index_type     <> 'IOT - TOP'
  1937.               UNION ALL
  1938.               SELECT
  1939.                      i.owner
  1940.                    , i.index_name
  1941.                    , i.table_name
  1942.                    , DECODE(
  1943.                              p.last_analyzed
  1944.                             ,null,'NO'
  1945.                             ,'YES'
  1946.                            )                  AS analyzed
  1947.               FROM
  1948.                      dba_indexes            i
  1949.                    , dba_ind_subpartitions  p
  1950.               WHERE
  1951.                          p.tablespace_name = '$tblsp'
  1952.                      AND i.owner           = p.index_owner
  1953.                      AND i.index_name      = p.index_name
  1954.                      AND i.index_type      <> 'IOT - TOP'
  1955.             )
  1956.        WHERE
  1957.              (
  1958.                  owner
  1959.                , table_name
  1960.              ) NOT IN (
  1961.                         SELECT
  1962.                                owner
  1963.                              , segment_name
  1964.                         FROM
  1965.                                THE_PARTITIONS
  1966.                       )
  1967.        GROUP
  1968.           BY
  1969.               owner
  1970.             , index_name
  1971.             , table_name
  1972.       ";
  1973.  
  1974.   $dbh->do( $stmt );
  1975.  
  1976.   # This query produces a list of THE IOTs -- non-partition index organized
  1977.   # tables which reside in THE TABLESPACE or partitioned index organized
  1978.   # tables which have at least one partition in THE TABLESPACE.
  1979.   # 
  1980.   $stmt =
  1981.       "
  1982.        CREATE GLOBAL TEMPORARY TABLE the_IOTs
  1983.        ON COMMIT PRESERVE ROWS
  1984.        AS
  1985.        SELECT
  1986.               owner
  1987.             , table_name
  1988.             , MAX(analyzed)        AS analyzed
  1989.        FROM
  1990.             (
  1991.               SELECT
  1992.                      owner
  1993.                    , table_name
  1994.                    , DECODE(
  1995.                              last_analyzed
  1996.                             ,null,'NO'
  1997.                             ,'YES'
  1998.                            )                  AS analyzed
  1999.               FROM
  2000.                      dba_indexes
  2001.               WHERE
  2002.                          tablespace_name = '$tblsp'
  2003.                      AND index_type      = 'IOT - TOP'
  2004.               UNION ALL
  2005.               SELECT
  2006.                      i.owner
  2007.                    , i.table_name
  2008.                    , DECODE(
  2009.                              p.last_analyzed
  2010.                             ,null,'NO'
  2011.                             ,'YES'
  2012.                            )                  AS analyzed
  2013.               FROM
  2014.                      dba_indexes         i
  2015.                    , dba_ind_partitions  p
  2016.               WHERE
  2017.                          p.tablespace_name = '$tblsp'
  2018.                      AND i.index_type      = 'IOT - TOP'
  2019.                      AND i.owner           = p.index_owner
  2020.                      AND i.table_name      = p.index_name
  2021.               UNION ALL
  2022.               SELECT
  2023.                      i.owner
  2024.                    , i.table_name
  2025.                    , DECODE(
  2026.                              p.last_analyzed
  2027.                             ,null,'NO'
  2028.                             ,'YES'
  2029.                            )                  AS analyzed
  2030.               FROM
  2031.                      dba_indexes            i
  2032.                    , dba_ind_subpartitions  p
  2033.               WHERE
  2034.                          p.tablespace_name = '$tblsp'
  2035.                      AND i.index_type      = 'IOT - TOP'
  2036.                      AND i.owner           = p.index_owner
  2037.                      AND i.table_name      = p.index_name
  2038.             )
  2039.        GROUP
  2040.           BY
  2041.               owner
  2042.             , table_name
  2043.       ";
  2044.  
  2045.   $dbh->do( $stmt );
  2046.  
  2047.   # This query produces a list of THE TABLES -- non-partitioned tables which
  2048.   # reside in THE TABLESPACE or partitioned tables which have at every
  2049.   # partition in THE TABLESPACE.
  2050.   #
  2051.   $stmt =
  2052.       "
  2053.        CREATE GLOBAL TEMPORARY TABLE the_tables
  2054.        ON COMMIT PRESERVE ROWS
  2055.        AS
  2056.        SELECT
  2057.               owner
  2058.             , table_name
  2059.             , MAX(analyzed)        AS analyzed
  2060.        FROM
  2061.             (
  2062.               SELECT
  2063.                      owner
  2064.                    , table_name
  2065.                    , DECODE(
  2066.                              last_analyzed
  2067.                             ,null,'NO'
  2068.                             ,'YES'
  2069.                            )                  AS analyzed
  2070.               FROM
  2071.                      dba_tables
  2072.               WHERE
  2073.                      tablespace_name   = '$tblsp'
  2074.               UNION ALL
  2075.               SELECT
  2076.                      table_owner
  2077.                    , table_name
  2078.                    , DECODE(
  2079.                              last_analyzed
  2080.                             ,null,'NO'
  2081.                             ,'YES'
  2082.                            )                  AS analyzed
  2083.               FROM
  2084.                      dba_tab_partitions  t
  2085.               WHERE
  2086.                          tablespace_name   = '$tblsp'
  2087.                      AND NOT EXISTS (
  2088.                                       SELECT
  2089.                                              null
  2090.                                       FROM
  2091.                                              dba_tab_partitions
  2092.                                       WHERE
  2093.                                                  table_owner = t.table_owner
  2094.                                              AND table_name  = t.table_name
  2095.                                              AND tablespace_name <> '$tblsp'
  2096.                                       UNION ALL
  2097.                                       SELECT
  2098.                                              null
  2099.                                       FROM
  2100.                                              dba_tab_subpartitions
  2101.                                       WHERE
  2102.                                                  table_owner = t.table_owner
  2103.                                              AND table_name  = t.table_name
  2104.                                              AND tablespace_name <> '$tblsp'
  2105.                                     )
  2106.               UNION ALL
  2107.               SELECT
  2108.                      table_owner
  2109.                    , table_name
  2110.                    , DECODE(
  2111.                              last_analyzed
  2112.                             ,null,'NO'
  2113.                             ,'YES'
  2114.                            )                  AS analyzed
  2115.               FROM
  2116.                      dba_tab_subpartitions  t
  2117.               WHERE
  2118.                          tablespace_name   = '$tblsp'
  2119.                      AND NOT EXISTS (
  2120.                                       SELECT
  2121.                                              null
  2122.                                       FROM
  2123.                                              dba_tab_subpartitions
  2124.                                       WHERE
  2125.                                                  table_owner = t.table_owner
  2126.                                              AND table_name  = t.table_name
  2127.                                              AND tablespace_name <> '$tblsp'
  2128.                                     )
  2129.               -- Ignore Snapshots/Materialized Views.
  2130.               -- Yeah, it's a cop out.
  2131.               MINUS
  2132.               SELECT
  2133.                      owner
  2134.                    , table_name
  2135.                    , 'YES'                    AS analyzed
  2136.               FROM
  2137.                      dba_snapshots
  2138.               MINUS
  2139.               SELECT
  2140.                      owner
  2141.                    , table_name
  2142.                    , 'NO'                     AS analyzed
  2143.               FROM
  2144.                      dba_snapshots
  2145.             )
  2146.        GROUP
  2147.           BY
  2148.               owner
  2149.             , table_name
  2150.       ";
  2151.  
  2152.   $dbh->do( $stmt );
  2153. }
  2154.  
  2155. # sub move
  2156. # Formats an ALTER TABLE MOVE [SUB]PARTITION statement
  2157. #
  2158. sub move
  2159. {
  2160.   my ( 
  2161.        $owner, 
  2162.        $table, 
  2163.        $partition, 
  2164.        $type, 
  2165.        $part_type,
  2166.        $analyzed,
  2167.        $tblsp,
  2168.      ) = @_;
  2169.  
  2170.   my $sql = "PROMPT " .
  2171.             "ALTER TABLE \L$owner.$table \UMOVE $type \L$partition\n\n" .
  2172.             "ALTER TABLE \L$owner.$table \UMOVE $type \L$partition\n" .
  2173.             "TABLESPACE \L$tblsp\n";
  2174.  
  2175.   # Can't specify INITIAL/NEXT on HASH partitions,
  2176.   # and all subpartitions are currently HASH
  2177.   if ( $type eq 'PARTITION' and $part_type eq 'RANGE' )
  2178.   {
  2179.     $sql .= "STORAGE\n" .
  2180.             "(\n" .
  2181.             "  INITIAL  2K\n" .
  2182.             "  NEXT     2K\n" .
  2183.             ") ";
  2184.   }
  2185.  
  2186.   return $sql .= ";\n\n";
  2187. }
  2188.  
  2189. # sub print_help
  2190. #
  2191. # Displays a description of each argument.
  2192. #
  2193. sub print_help
  2194. {
  2195.   print "
  2196.   Usage:  defrag.pl [OPTION] [OPTION]...
  2197.  
  2198.   ?, -?, -h, --help   Prints this help.
  2199.  
  2200.   --tablespace=TABLESPACE
  2201.  
  2202.            Drop/recreate all objects in the named tablespace -- tables,
  2203.            table partitions, non-partitioned indexes and indexes which
  2204.            have even one partition in the named tablespace.
  2205.  
  2206.            This argument is REQUIRED.
  2207.  
  2208.   --alttablespace=TABLESPACE
  2209.  
  2210.            If table partition(s) is(are) part of the defrag, a
  2211.            substitute, placeholder partition is created in this
  2212.            tablespace.  If not given, tablespace USERS will be used if
  2213.            present, otherwise the named tablespace.  If the argument
  2214.            is not given, and if there are partitioned tables in the
  2215.            named tablespace, and if there is not a USERS tablespace,
  2216.            the placeholder partitions will probably prevent a complete
  2217.            coalesce of the named tablesapace.  This argument is highly
  2218.            recommended.
  2219.  
  2220.   --expdir=PATH *
  2221.  
  2222.            Directory to place the import/export .par files.  Defaults to
  2223.            environment variable DBA_EXP, or to the current directory.
  2224.  
  2225.   --logdir=PATH *
  2226.  
  2227.            Directory to place the import/export .log files, as well
  2228.            as the SPOOLed .log files created by SQL*Plus.  Defaults to
  2229.            environment variable DBA_LOG, or to the current directory.
  2230.  
  2231.   --password=PASSWORD
  2232.  
  2233.            User's password.  Not required if user is authenticated
  2234.            externally.  Respresents a security risk on Unix systems.
  2235.  
  2236.            If USER is given and PASSWORD is not, program will prompt
  2237.            for PASSWORD.  This would be preferable to entering the
  2238.            password on the command line, since the password will then
  2239.            not be visible in a 'ps' command.
  2240.  
  2241.   --prefix=STRING *
  2242.  
  2243.            The leading portion of all filenames.  Defaults to 'defrag_',
  2244.            and may be '' (in which case filenames will begin with the
  2245.            name of the tablespace).
  2246.  
  2247.   --sid=SID *
  2248.  
  2249.            The SID or service used to connect to Oracle.  If omitted,
  2250.            the connection will be to the instance identified in
  2251.            environment variable ORACLE_SID.
  2252.  
  2253.   --resize=STRING *
  2254.  
  2255.            In the CREATE statement, objects are given INITIAL and NEXT
  2256.            extent sizes, appropriate for objects having the number of
  2257.            blocks used.  This is a colon delimited string consisting
  2258.            of n sets of LIMIT:INITIAL:NEXT.  LIMIT is expressed in
  2259.            Database Blocks.  The highest LIMIT may contain the string
  2260.            'UNLIMITED', and in any event will be forced to be so by
  2261.            DDL::Oracle.
  2262.  
  2263.   --sqldir=PATH *
  2264.  
  2265.            Directory to place the SQL (.sql) files.  Defaults to
  2266.            environment variable DBA_SQL, or to the current directory.
  2267.  
  2268.   --user=USERNAME
  2269.  
  2270.            Connects to Oracle as this user.  Defaults to operating
  2271.            system username.
  2272.  
  2273.   *  Items marked with '*' are saved in a file named .defragrc,
  2274.      stored in the user's HOME directory.  If omitted in subsequent
  2275.      usages of defrag.pl, these entries will be reused unless a
  2276.      new entry is assigned at that time.
  2277.  
  2278.   ";
  2279.  
  2280.   $text = "
  2281.   Program 'defrag.pl' uses 5 main SQL statements to retrieve record sets which
  2282.   form the basis of generated DDL.  They are sometimes UNIONed, sometimes 
  2283.   MINUSed, etc., to refine the record sets.  The queries are:
  2284.  
  2285.   THE TABLESPACE -- the Tablspace named by the '--tablespace=<name>' argument.
  2286.  
  2287.   THE CONSTRAINTS -- provides a substitute for DBA_CONSTRAINTS, sans column
  2288.   SEARCH_CONDITION.
  2289.  
  2290.   THE TABLES -- provides a list of Owner/Table_name's which fully reside in
  2291.   THE TABLESPACE.  These are non-partitioned tables plus partitioned tables
  2292.   where every partition and subpartition reside in THE TABLESPACE.  This list 
  2293.   excludes IOT tables.
  2294.  
  2295.   THE IOTS -- provides a list of Owner/Table_name's which fully or partially
  2296.   reside in THE TABLESPACE.  In other words, if a partitioned IOT table has
  2297.   even one partition in THE TABLESPACE, it is included in this list.  Reasons
  2298.   these are in  a separate list from THE TABLES include the fact that their 
  2299.   Primary Key is part of the CREATE TABLE syntax, and there are never other 
  2300.   indexes on them,
  2301.  
  2302.   THE INDEXES -- provides a list of Owner/Index_name/Table_name's for indexes
  2303.   not belonging to THE TABLES but which fully or partially reside in THE
  2304.   TABLESPACE.  In other words, a partitioned index with even one partition in
  2305.   THE TABLESPACE is included in this list.
  2306.  
  2307.   The data in THE TABLES and THE IOTS will be exported, after which members of
  2308.   all 3 of the lists will be dropped before THE TABLESPACE is coalesced into
  2309.   as few as 1 extent per datafile.
  2310.  
  2311.   THE PARTITIONS -- provides Owner/Table_name/Partition_name/Segment_type's 
  2312.   for all partitions and subpartitions not belonging to THE TABLES nor to THE
  2313.   IOTS but which are located in THE TABLESPACE.  If any of these exist, the
  2314.   first step will be to perform a 'safety' export of their data directly from
  2315.   THE PARTITIONS.  Under normal circumstances, this export is not used.
  2316.   Rather, for each partition a corresponding 'temp' table is built matching
  2317.   the partition in structure, indexes and Primary Key.  The temp table is then
  2318.   EXCHANGED with the partition; this results in the temp table holding the
  2319.   data and the partition becoming empty.  The empty partition is moved to the
  2320.   alternate tablespace before the coalescing takes place.  The temp table is
  2321.   then treated like a member of THE TABLES (i.e., exported, dropped,
  2322.   recreated, indexed, imported, etc.).  After the temp table has its data
  2323.   imported, it is again EXCHANGED with its original partition, and thus the
  2324.   data once again becomes part of the table in its new, properly sized 
  2325.   segment.
  2326.  
  2327.   Note that nothing is done with indexes on the tables of THE PARTITIONS.  In
  2328.   the event that such an index or a partition thereof happens to reside in THE
  2329.   TABLESPACE, it will still be there after all other objects have been dropped 
  2330.   or moved eleehwhere.  Likewise, unless an alternate tablespace other than
  2331.   THE TABLESPACE is given (or if the named alternate tablespace does not
  2332.   exist), then the empty partition segments will also remain in THE TABLESPACE.
  2333.   If either of these conditions occurs, the THE TABLESPACE will not be
  2334.   completely empty when it is coalesced.  This is not necessarily a big
  2335.   problem, it is just not as clean as when THE TABLESPACE becomes completely
  2336.   empty before it is coalesced.
  2337.  
  2338.   The following descriptions of the 'Statement Groups' show the sequence of
  2339.   statments used to defragment THE TABLESPACE.  These DDL statements are in
  2340.   3 to 5 files.  Shell scripts are provided which perform the statements in
  2341.   the correct sequence, intermingled with the exports and imports.  The user
  2342.   should check the execution of each shell script for errors before continuing
  2343.   with the next step.  Within the SQL files, each group of statements is
  2344.   delineated by a header record which refers to a 'Statement Group Number'.
  2345.   These groups are defined below.
  2346.   
  2347.   EXPORT the data from THE PARTITIONS. (If all goes well, we won't use this.)
  2348.   
  2349.    1.  For each member of THE PARTITIONS:
  2350.          a.  Create a Temp table.
  2351.          b.  Add appropriate indexes.
  2352.          c.  Add a PK, if any.
  2353.          d.  EXCHANGE the Temp table with the partition.
  2354.          e.  MOVE the [now empty] Temp table to the alternate tablespace.
  2355.   
  2356.   EXPORT the data from THE TABLES, THE IOTS and the Temp tables.
  2357.   
  2358.    2.  DROP the Temp tables created in Group #1.
  2359.   
  2360.    3.  DROP all Foreign Keys referencing THE TABLES, THE IOTS or the tables
  2361.        of THE INDEXES.
  2362.   
  2363.    4.  DROP members of THE TABLES and THE IOTS.  Note: this DROPs all
  2364.        constrints on these tables.
  2365.  
  2366.    5.  DROP Primary Keys, Unique Constraints and Check Constraints on the
  2367.        tables of THE INDEXES. 
  2368.  
  2369.    6.  DROP members of THE INDEXES unless they enforce a Primay Key or Unique
  2370.        Constraint of the same name -- those that do disappeared in Group #5.
  2371.        Note: this will generate DROP INDEX statements for PK/UK's if the 
  2372.        Constraint name differs from the Index name (e.g., system generated
  2373.        names).  It won't cause any harm, but it will show an error in the log
  2374.        file spooled in SQL*Plus; these should be ignored.  Maybe we'll fix
  2375.        this someday.
  2376.  
  2377.    7.  CREATE the Temp tables.
  2378.   
  2379.    8.  CREATE members of THE TABLES and THE IOTS.
  2380.  
  2381.   IMPORT the data for THE TABLES, THE IOTS and the Temp tables.
  2382.   
  2383.    9.  CREATE indexes and PK's on the Temp tables.  EXCHANGE them with their
  2384.        corresponding partition, and DROP the now empty Temp tables.
  2385.   
  2386.   10.  CREATE indexes on THE TABLES, plus THE INDEXES themselves.
  2387.  
  2388.   11.  CREATE all Constraints on THE TABLES.
  2389.  
  2390.   12.  CREATE Check Cosntraints on THE IOTS.
  2391.  
  2392.   13.  CREATE Foreign Keys referencing THE TABLES, THE IOTS or the tables
  2393.        of THE INDEXES.
  2394.  
  2395.   14.  REBUILD non-partitioned or Global partitioned indexes on THE PARTITIONS
  2396.        (these were marked UNUSABLE during the partition EXCHANGE).
  2397.  
  2398.   ONLY IF PROBLEMS OCCURED DURING EXECUTION OF GROUP #1:
  2399.  
  2400.   15.  DROP the Temp tables.
  2401.  
  2402.   IMPORT the data for THE PARTITIONS.
  2403.  
  2404.   ";
  2405.  
  2406.   write_file( "./README.defrag", $text, '' );
  2407.  
  2408.   print "
  2409.   Also, see the 'README.defrag' which was just written in this directory
  2410.   for information about the DDL statements generated and their sequence.
  2411.   ";
  2412.  
  2413.   return;
  2414. }
  2415.  
  2416. # sub set_defaults
  2417. #
  2418. # If file HOME/.defragrc exists, reads its contents into hash %args.
  2419. # Otherwise, fill the hash with arbitrary defaults.
  2420. #
  2421. sub set_defaults
  2422. {
  2423.   if ( -e "$home/.defragrc" ) 
  2424.   {
  2425.     # We've been here before -- set up per .defragrc
  2426.     open RC, "<$home/.defragrc"    or die "Can't open $home/.defragrc:  $!\n";
  2427.     while ( <RC> ) 
  2428.     {
  2429.       chomp;                       # no newline
  2430.       s/#.*//;                     # no comments
  2431.       s/^\s+//;                    # no leading white space
  2432.       s/\s+$//;                    # no trailing white space
  2433.       next unless length;          # anything left? (or was blank)
  2434.       my ( $key, $value ) = split( /\s*=\s*/, $_, 2 );
  2435.       $args{ $key } = $value;
  2436.     }
  2437.     close RC                       or die "Can't close $home/.defragrc:  $!\n";
  2438.  
  2439.     # Just in case they farkled the .defragrc file
  2440.     $args{ expdir } = '.'       unless $args{ expdir };
  2441.     $args{ sqldir } = '.'       unless $args{ sqldir };
  2442.     $args{ logdir } = '.'       unless $args{ logdir };
  2443.     $args{ prefix } = 'defrag_' unless $args{ prefix };
  2444.   }
  2445.   else 
  2446.   {
  2447.     # First time for this user
  2448.     $args{ expdir } = $ENV{ DBA_EXP }    || ".";
  2449.     $args{ sqldir } = $ENV{ DBA_SQL }    || ".";
  2450.     $args{ logdir } = $ENV{ DBA_LOG }    || $ENV{ LOGDIR } || ".";
  2451.     $args{ prefix } = "defrag_";
  2452.   }
  2453.   Getopt::Long::Configure( 'passthrough' );
  2454. }
  2455.  
  2456. # sub trunc
  2457. #
  2458. # Formats a TRUNCATE statement for the supplied [sub]partition
  2459. #
  2460. sub trunc
  2461. {
  2462.   my ( $owner, $table, $partition, $type ) = @_;
  2463.  
  2464.   return  "PROMPT " .
  2465.           "ALTER TABLE \L$owner.$table \UTRUNCATE $type \L$partition  \n\n" .
  2466.           "ALTER TABLE \L$owner.$table \UTRUNCATE $type \L$partition ;\n\n";
  2467. }
  2468.  
  2469. # sub unique_nbr
  2470. #
  2471. # Generates a unique 6-digit number for use in Temp Table names
  2472. #
  2473. sub unique_nbr
  2474. {
  2475.   my $nbr;
  2476.  
  2477.   while( 1 )
  2478.   {
  2479.     $nbr = int( rand 900000 ) + 100000;
  2480.     $uniq{ $nbr }++;
  2481.     last unless $uniq{ $nbr } > 1;
  2482.   }
  2483.  
  2484.   return $nbr
  2485. }
  2486.  
  2487. # sub validate_log_names
  2488. #
  2489. # Ensures that log files are writeable.  These files are not actually
  2490. # OPENed during the program, so this check is not foolproof, but it
  2491. # might save a little time just in case the filename is unwriteable.
  2492. #
  2493. sub validate_log_names
  2494. {
  2495.   my ( $aref ) = @_;
  2496.  
  2497.   foreach my $file ( @$aref )
  2498.   {
  2499.     die "\n***Error:  Log file $file\n",
  2500.         "           is not writeable\n",
  2501.         "\n$0 aborted,\n\n"
  2502.       unless (
  2503.                   -e $file and -w $file
  2504.                or not -e $file
  2505.              );
  2506.   }
  2507. }
  2508.  
  2509. # sub write_file
  2510. #
  2511. # Opens, writes, closes a .sql or .par file
  2512. #
  2513. sub write_file
  2514. {
  2515.   my ( $filename, $text, $remark ) = @_;
  2516.  
  2517.   open FILE, ">$filename"     or die "Can't open $filename: $!\n";
  2518.   write_header( \*FILE, $filename, $remark );
  2519.   print FILE $text,
  2520.              "$remark --- END OF FILE ---\n\n";
  2521.   close FILE                  or die "Can't close $filename: $!\n";
  2522. }
  2523.  
  2524. # sub write_header
  2525. #
  2526. # Creates a 7-line header in the supplied file, marked as comments.
  2527. #
  2528. sub write_header
  2529. {
  2530.   my ( $fh, $filename, $remark ) = @_;
  2531.  
  2532.   print $fh "$remark $filename\n",
  2533.             "$remark \n",
  2534.             "$remark Created by $0\n",
  2535.             "$remark on ", scalar localtime,"\n\n\n\n";
  2536. }
  2537.  
  2538. # $Log: defrag.pl,v $
  2539. # Revision 1.18  2001/04/28 13:51:28  rvsutherland
  2540. # Fixed to work on Windows [I think].
  2541. #
  2542. # Revision 1.17  2001/01/27 16:23:25  rvsutherland
  2543. # Upgraded to handle tablespaces having no tables (only indexes).
  2544. #
  2545. # Revision 1.16  2001/01/14 16:47:55  rvsutherland
  2546. # Nominal changes for version 0.32
  2547. #
  2548. # Revision 1.15  2001/01/07 16:44:54  rvsutherland
  2549. # Changed 'WITHOUT' to 'without' in success message of scripts
  2550. #
  2551. # Revision 1.14  2001/01/01 22:43:21  rvsutherland
  2552. # Altered shell scripts to be completely self checking.
  2553. # Added driver shell script to call all other scripts, so that defragging
  2554. #    could take place in background while DBA eats pizza.
  2555. #
  2556. # Revision 1.13  2001/01/01 12:59:52  rvsutherland
  2557. # Fixed bug in export parfile.
  2558. #
  2559. # Revision 1.12  2000/12/31 12:51:59  rvsutherland
  2560. # Added ANALYZE TABLE/INDEX following Import, for previously analyzed objects
  2561. #
  2562. # Revision 1.11  2000/12/31 00:46:58  rvsutherland
  2563. # Before starting, verified that Log files were writiable.
  2564. # Modified queries in anticipation of adding ANALYZE TABLE statements
  2565. #
  2566. # Revision 1.10  2000/12/28 21:45:25  rvsutherland
  2567. # Upgraded to handle table names containing '$'.
  2568. # Corrected Statement Group 15 to MOVE the partitions back to THE TABLESPACE.
  2569. # Put all Log files in logdir (were going to sqldir -- go figure)
  2570. # Corrected NEXT size if object reached last tier (was null)
  2571. #
  2572. # Revision 1.9  2000/12/09 17:38:56  rvsutherland
  2573. # Additional tuning refinements.
  2574. # Minor cleanup of code.
  2575. #
  2576. # Revision 1.8  2000/12/06 00:43:45  rvsutherland
  2577. # Significant performance improvements.
  2578. # No, make that MAJOR gains (i.e., orders of magnitude for large databases).
  2579. # To wit:
  2580. #   Replaced convoluted Dictionary views with 8i Temporary Tables
  2581. #   Widely (but not entirely) switched to bind variables (was interpolated,
  2582. #     causing reparsing in most cases).
  2583. # Also fixed error on REBUILD of Global and non-partitioned indexes.
  2584. #
  2585. # Revision 1.7  2000/12/02 14:06:20  rvsutherland
  2586. # Completed 'exchange' method for handling partitions,
  2587. # including REBUILD of UNUSABLE indexes.
  2588. # Removed 'resize' method for handling partitions.
  2589. #
  2590. # Revision 1.6  2000/11/26 20:10:54  rvsutherland
  2591. # Added 'exchange' method for handling partitions.  Will probably
  2592. # remove the 'resize' method next update.
  2593. #
  2594. # Revision 1.5  2000/11/24 18:36:00  rvsutherland
  2595. # Restructured file writes
  2596. # Revamped 'resize' method for handling partitions
  2597. #
  2598. # Revision 1.4  2000/11/19 20:08:58  rvsutherland
  2599. # Added 'resize' partitions option.
  2600. # Restructured file creation.
  2601. # Added shell scripts to simplify executing generated files.
  2602. # Modified selection of IOT tables (now handled same as indexes)
  2603. # Added validation of input arguments -- meaning we now check for
  2604. # hanging chad and pregnant votes  ;-)
  2605. #
  2606. # Revision 1.3  2000/11/17 21:35:53  rvsutherland
  2607. # Commented out Direct Path export -- Import has a bug (at least on Linux)
  2608. #
  2609. # Revision 1.2  2000/11/16 09:14:38  rvsutherland
  2610. # Major restructure to take advantage of DDL::Oracle.pm
  2611. #
  2612.  
  2613. __END__
  2614.  
  2615. ########################################################################
  2616.  
  2617. =head1 NAME
  2618.  
  2619. defrag.pl -- Creates SQL*Plus command files to defragment a tablespace.
  2620.  
  2621. =head1 SYNOPSIS
  2622.  
  2623. [ ? | -? | -h | --help ]
  2624.  
  2625. --tablespace=TABLESPACE
  2626.  
  2627. [--alttablespace=TABLESPACE]
  2628.  
  2629. [--expdir=PATH]
  2630.  
  2631. [--logdir=PATH]
  2632.  
  2633. [--resize=STRING]
  2634.  
  2635. [--sqldir=PATH]
  2636.  
  2637. [--user=USERNAME]
  2638.  
  2639. [--password=PASSWORD]
  2640.  
  2641. [--prefix=PREFIX]
  2642.  
  2643. [--sid=SID]
  2644.  
  2645. [--resize=STRING]
  2646.  
  2647. Note:  descriptions of each of these arguments are provided via 'help',
  2648. which may be displayed by entering 'defrag.pl' without any arguments.
  2649.  
  2650. =head1 DESCRIPTION
  2651.  
  2652. Creates command files to defragment (reorganize) an entire Oracle
  2653. Tablespace.  Arguments are specified on the command line.
  2654.  
  2655. A "defrag" is usually performed to recapture the little fragments of
  2656. unused (and unusable) space that tend to accumulate in Oracle
  2657. tablespaces when objects are repeatedly created and dropped.. To fix
  2658. this, data is first exported.  Objects are then dropped and the
  2659. tablespace is "coalesced" into one large extent of available space.  The
  2660. objects are then recreated using either the default sizing algorithm or a
  2661. user supplied algorithm, and the data is imported.  Space utilized is then
  2662. contiguous, and the unused free space has been captured for use.
  2663.  
  2664. The steps in the process are:
  2665.  
  2666.     1.  Export all objects in the tablespace (tables, indexes, partitions).
  2667.     2.  Drop all objects.
  2668.     3.  Coalesce the tablespace.
  2669.     4.  Create all tables and partitions, resized appropriately.
  2670.     5.  Import the data into the new structures.
  2671.     6.  Recreate the indexes.
  2672.     7.  Restore all constraints.
  2673.  
  2674. =head1 TO DO
  2675.  
  2676. =head1 BUGS
  2677.  
  2678. =head1 FILES
  2679.  
  2680. The names and number of files output varies according to the Tablespace
  2681. specified and the options selected.  All .sql and .log files and shell
  2682. scripts produced are displayed on STDOUT during the execution of the program.
  2683.  
  2684. Also, see 'README.defrag', which will be created when Help is displayed (by
  2685. entering 'defrag.pl' without any arguments).
  2686.  
  2687. =head1 AUTHOR
  2688.  
  2689.  Richard V. Sutherland
  2690.  rvsutherland@yahoo.com
  2691.  
  2692. =head1 COPYRIGHT
  2693.  
  2694. Copyright (c) 2000, 2001 Richard V. Sutherland.  All rights reserved.
  2695. This script is free software.  It may be used, redistributed, and/or
  2696. modified under the same terms as Perl itself.  See:
  2697.  
  2698.     http://www.perl.com/perl/misc/Artistic.html
  2699.  
  2700. =cut
  2701.  
  2702.