//***************************************************************************
// (c) Copyright IBM Corp. 2007 All rights reserved.
// 
// The following sample of source code ("Sample") is owned by International 
// Business Machines Corporation or one of its subsidiaries ("IBM") and is 
// copyrighted and licensed, not sold. You may use, copy, modify, and 
// distribute the Sample in any form without payment to IBM, for the purpose of 
// assisting you in the development of your applications.
// 
// The Sample code is provided to you on an "AS IS" basis, without warranty of 
// any kind. IBM HEREBY EXPRESSLY DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR 
// IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Some jurisdictions do 
// not allow for the exclusion or limitation of implied warranties, so the above 
// limitations or exclusions may not apply to you. IBM shall not be liable for 
// any damages you suffer as a result of using, copying, modifying or 
// distributing the Sample, even if IBM has been advised of the possibility of 
// such damages.
//***************************************************************************
//
// SOURCE FILE NAME: TbRowcompress.sqlj
//
// SAMPLE: How to perform row compression on a table
//
//         This sample shows:
//         1. How to enable the row compression after a table is created.
//         2. How to enable the row compression during table creation.
//         3. Usage of the options to REORG to use the exiting dictionary
//            or creating a new dictionary.
//         4. How to estimate the effectiveness of the compression.
//
//         This sample should be run using the following steps:
//         1.Compile the program with the following command:
//           javac TbRowcompress.java
//
//         2.The sample should be run using the following command
//           java TbRowcompress <path for dummy file>
//           The fenced user id must be able to create or overwrite files in
//           the directory specified.This directory must be a full path on
//           the server. The dummy file 'dummy.del' must
//           exist before the sample is run.
//
// SQL STATEMENTS USED:
//         ALTER TABLE
//         COMMIT
//         DELETE
//         EXPORT
//         FETCH
//         IMPORT
//         INSERT
//         INSPECT
//         REORG
//         RUNSTATS
//         SELECT
//         UPDATE
//
// JAVA 2 CLASSES USED:
//         Statement
//         CallableStatement
//         ResultSet
//
// Classes used from Util.java are:
//         Db
//         Data
//         JdbcException
//
// PREQUISITES : 1. Create the pre-requisite tables by running the command:
//                    TbRowcompressScrpt
//                  Alternatively,you can run the command:
//                    db2 -tvf TbRowcompress_setup.db2
//
// EXECUTION : 1. Compile the sample using:
//                   bldsqlj TbRowcompress <userid> <password> <server_name> <port_number> <db_name>
//             2. Run the sample as:
//                   java TbRowcompress
//             3. Perform a clean up once the sample has been executed using:
//                   db2 -tvf TbRowcompress_cleanup.db2
//
//               TbRowcompress.java 
//***************************************************************************
//
// For more information on the sample programs, see the README file.
//
// For information on developing Java applications see the Developing Java Applications book.
//
// For information on using SQL statements, see the SQL Reference.
//
// For the latest information on programming, compiling, and running DB2
// applications, visit the DB2 Information Center at
//     http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/index.jsp
//***************************************************************************

import java.lang.*;
import java.sql.*;
import sqlj.runtime.*;
import sqlj.runtime.ref.*;

#sql iterator TbRowcompress_Cursor0(String);
#sql iterator TbRowcompress_Cursor1(int, int);
#sql iterator TbRowcompress_Cursor2(int, int, int, int, int);

class TbRowcompress
{
  public static void main(String argv[])
  {
      try
      {
        Connection con;
        DefaultContext ctx;

        // initialize DB2Driver and establish database connection.
        Class.forName("com.ibm.db2.jcc.DB2Driver").newInstance();
        con = DriverManager.getConnection("jdbc:db2:SAMPLE");

        ctx = new DefaultContext( con );
        DefaultContext.setDefaultContext(ctx);

        String path = argv[0];

        System.out.println(
          "THIS SAMPLE SHOWS HOW TO PERFROM ROW COMPRESSION ON A TABLE.\n" +
          "\n-------------------------------------------------------------\n");

         // to Load table data into a file.
        getLoadData(con, path);

        // to enable row compression on table.
        enableRowCompressionForTables(con, path);

        // to disable row compression on tables.
        disableRowCompressionForTables(con, path);

        // to inspect the compression.
        inspectCompression(con, path);

        // disconnect from the 'sample' database
        con.close();

      }
      catch (Exception e)
      {
        SqljException sqljExc = new SqljException(e);
        sqljExc.handle();
      }

  } // main

  // this function shows how to prepare data for load
  static void getLoadData(Connection con, String path) throws SQLException
  {
    try
    {
      System.out.println(
        "\nThe temp table is created in the setup script \n" +
        "TbRowcompress_setup.db2 using the command \n" +
        "'CREATE TABLE temp(empno INT, sal INT)' \n");

      // insert data into the table and export the data in order to obtain
      // dummy.del file in the required format for load.

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  INSERT \n" +
        "TO INSERT DATA INTO THE TABLE \n" +
        "\n    Perform:\n" +
        "    INSERT INTO temp VALUES(100, 20000)\n" +
        "    INSERT INTO temp VALUES(200, 30000)\n" +
        "    INSERT INTO temp VALUES(100, 30500)\n" +
        "    INSERT INTO temp VALUES(300, 20000)\n" +
        "    INSERT INTO temp VALUES(400, 30000)");

      // insert data into the table
      #sql {INSERT INTO temp VALUES(100, 20000)};
      #sql {INSERT INTO temp VALUES(200, 30000)};
      #sql {INSERT INTO temp VALUES(100, 30500)};
      #sql {INSERT INTO temp VALUES(300, 20000)};
      #sql {INSERT INTO temp VALUES(400, 30000)};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  EXPORT \n" +
        "TO EXPORT TABLE DATA INTO A FILE \n" +
        "\n    Perform:\n" +
        "    EXPORT TO dummy.del OF DEL SELECT * FROM temp");

      // export data into a dummy file
      // call the stored procedure ADMIN_CMD for EXPORT
      System.out.println();
      System.out.println("Call stored procedure named SYSPROC.ADMIN_CMD");

      String str ="CALL SYSPROC.ADMIN_CMD" +
                  "('EXPORT TO " +
                  path +
                  "dummy.del OF DEL SELECT * FROM temp')";

      Statement stmt = con.createStatement();
      stmt.execute(str);
      stmt.close();
    }
    catch (Exception e)
    {
      SqljException sqljExc = new SqljException(e);
      sqljExc.handle();
    }
  } // getLoadData

  // this function shows how to enable row compression for tables
  static void enableRowCompressionForTables
                (Connection con, String path) throws SQLException
  {
    try
    {
      System.out.println(
        "\nTable empl1 is created in the setup script \n" +
        "TbRowcompress_setup.db2 with compression not enabled\n" +
        "at the time of table creation using the command\n" +
        "'CREATE TABLE empl1(emp_no INT, salary INT)' \n");

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  IMPORT \n" +
        "TO IMPORT THE DATA INTO THE TABLE \n" +
        "\n    Perform:\n" +
        "    IMPORT FROM dummy.del OF DEL INSERT INTO empl1");

      // import data from file
      // call the stored procedure ADMIN_CMD for IMPORT
      System.out.println();
      System.out.println("Call stored procedure named SYSPROC.ADMIN_CMD");

      String str ="CALL SYSPROC.ADMIN_CMD" +
                  "('IMPORT FROM " +
                  path +
                  "dummy.del OF DEL INSERT INTO empl1')";

      Statement stmt = con.createStatement();
      stmt.execute(str);
      stmt.close();

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  ALTER TABLE \n" +
        "TO ENABLE ROW COMPRESSION \n" +
        "\n    Perform:\n" +
        "    ALTER TABLE empl1 COMPRESS YES");

      // enable row compression
      #sql {ALTER TABLE empl1 COMPRESS YES};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  REORG \n" +
        "TO COMPRESS ROWS \n" +
        "\n    Perform:\n" +
        "    REORG TABLE empl1");

      // perform non-inplace reorg to compress rows and to retain
      // existing dictionary
      // call the stored procedure ADMIN_CMD for REORG
      System.out.println();
      System.out.println("Call stored procedure named SYSPROC.ADMIN_CMD");
      #sql {CALL SYSPROC.ADMIN_CMD('REORG TABLE empl1')};
      #sql {COMMIT};
    }
    catch (Exception e)
    {
      SqljException sqljExc = new SqljException(e);
      sqljExc.handle();
    }
  } // enableRowCompressionForTables

  // this function shows how to disable rowcompression on table
  static void disableRowCompressionForTables
                (Connection con, String path) throws SQLException
  {
    try
    {
      System.out.println(
        "\nTable empl2 is created in the setup script TbRowcompress_setup.db2\n" +
        "with compression enabled initially using the command \n" +
        "'CREATE TABLE empl2(emp_no INT, salary INT) COMPRESS YES' \n");

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  IMPORT \n" +
        "TO IMPORT THE DATA INTO THE TABLE \n" +
        "\n    Perform:\n" +
        "    IMPORT FROM dummy.del OF DEL INSERT INTO empl2");

      // import data into the table
      // call the stored procedure ADMIN_CMD for IMPORT
      System.out.println();
      System.out.println("Call stored procedure named SYSPROC.ADMIN_CMD");
      String str ="CALL SYSPROC.ADMIN_CMD" +
                  "('IMPORT FROM " +
                  path +
                  "dummy.del OF DEL INSERT INTO empl2')";

      Statement stmt = con.createStatement();
      stmt.execute(str);
      stmt.close();

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  REORG \n" +
        "TO COMPRESS ROWS \n" +
        "\n    Perform:\n" +
        "    REORG TABLE empl2");

      // perform reorg to compress rows
      // call the stored procedure ADMIN_CMD for REORG
      System.out.println();
      System.out.println("Call stored procedure named SYSPROC.ADMIN_CMD");
      #sql {CALL SYSPROC.ADMIN_CMD('REORG TABLE empl2')};
      #sql {COMMIT};


      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  INSERT \n" +
        "  UPDATE \n" +
        "  DELETE \n" +
        "TO INSERT, UPDATE OR DELETE DATA IN TABLE \n" +
        "\n    Perform:\n" +
        "    INSERT INTO empl2 VALUES(400, 30000)\n" +
        "    UPDATE empl2 SET salary = salary + 1000\n" +
        "    DELETE FROM empl2 WHERE emp_no = 200");

      // perform modifications on table
      #sql {INSERT INTO empl2 VALUES(400, 30000)};
      #sql {UPDATE empl2 SET salary = salary + 1000};
      #sql {DELETE FROM empl2 WHERE emp_no = 200};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  ALTER TABLE \n" +
        "TO DISABLE ROW COMPRESSION FOR THE TABLE \n" +
        "\n    Perform:\n" +
        "    ALTER TABLE empl2 COMPRESS NO");

      // disable row compression for the table
      #sql {ALTER TABLE empl2 COMPRESS NO};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  REORG TABLE \n" +
        "TO REORG THE TABLE AND REMOVE EXISTING DICTIONARY \n" +
        "\n    Perform:\n" +
        "    REORG TABLE empl2 RESETDICTIONARY");

      // Perform reorg to remove existing dictionary.
      // New dictionary will be created and all the rows processed
      // by the reorg are decompressed.
      // call the stored procedure ADMIN_CMD for REORG
      System.out.println();
      System.out.println("Call stored procedure named SYSPROC.ADMIN_CMD");
      #sql {CALL SYSPROC.ADMIN_CMD('REORG TABLE empl2 RESETDICTIONARY')};
      #sql {COMMIT};
    }
    catch (Exception e)
    {
      SqljException sqljExc = new SqljException(e);
      sqljExc.handle();
    }
  } // disableRowCompressionForTables

  // this function demonstrates the extent of row compression for table
  static void inspectCompression(Connection con, String path) throws SQLException
  {
    try
    {
      System.out.println(
        "\nTable empl3 is created in the setup script \n" +
        "TbRowcompress_setup.db2 using the command \n" +
        "'CREATE TABLE empl3(emp_no INT, salary INT)' \n");

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  IMPORT \n" +
        "TO IMPORT DATA INTO TABLE \n" +
        "\n    Perform:\n" +
        "    IMPORT FROM dummy.del OF DEL INSERT INTO empl3");

      // import data into the table
      // call the stored procedure ADMIN_CMD for IMPORT
      System.out.println();
      System.out.println("Call stored procedure named SYSPROC.ADMIN_CMD");
      String str ="CALL SYSPROC.ADMIN_CMD" +
                  "('IMPORT FROM " +
                  path +
                  "dummy.del OF DEL INSERT INTO empl3')";

      Statement stmt = con.createStatement();
      stmt.execute(str);
      stmt.close();

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  ALTER TABLE \n" +
        "TO ENABLE COMPRESSION \n" +
        "\n    Perform:\n" +
        "    ALTER TABLE empl3 COMPRESS YES");

      // enable row compression for the table
      #sql {ALTER TABLE empl3 COMPRESS YES};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  INSERT \n" +
        "TO INSERT DATA INTO THE TABLE \n" +
        "\n    Perform:\n" +
        "    INSERT INTO empl3 VALUES(400, 30000)");

      // insert some data into the table
      #sql {INSERT INTO empl3 VALUES(400, 30000)};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  INSPECT \n" +
        "TO ESTIMATE THE EFFECTIVENESS OF COMPRESSION \n" +
        "\n    Perform:\n" +
        "    INSPECT ROWCOMPESTIMATE TABLE NAME empl3 RESULTS KEEP result");

      // Perform inspect to estimate the effectiveness of compression.
      // Inspect has to be run before the REORG utility.
      // Inspect allows you to look over tablespaces and tables for their
      // architectural integrity.
      // 'result' file contains percentage of bytes saved from compression,
      // Percentage of rows ineligible for compression due to small row size,
      // Compression dictionary size, Expansion dictionary size etc.
      // To view the contents of 'result' file perform
      //    db2inspf result result.out; This formats the 'result' file to
      // readable form.

      String execCmd = "db2 INSPECT ROWCOMPESTIMATE TABLE NAME empl3" +
                       " RESULTS KEEP result";

      // execute the command
      Process p1 = Runtime.getRuntime().exec(execCmd);

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  REORG \n" +
        "TO REORG THE TABLE \n" +
        "\n    Perform:\n" +
        "    REORG TABLE empl3");

      // perform reorg on the table
      // call the stored procedure ADMIN_CMD for REORG
      System.out.println();
      System.out.println("Call stored procedure named SYSPROC.ADMIN_CMD");
      #sql {CALL SYSPROC.ADMIN_CMD('REORG TABLE empl3')};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  INSERT \n" +
        "TO INSERT DATA INTO THE TABLE \n" +
        "\n    Perform:\n" +
        "    INSERT INTO empl3 VALUES(500, 40000)");

      // all the rows will be compressed including the one inserted
      // after reorg
      #sql {INSERT INTO empl3 VALUES(500, 40000)};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  ALTER TABLE \n" +
        "TO DISABLE THE COMPRESSION \n" +
        "\n    Perform:\n" +
        "    ALTER TABLE empl3 COMPRESS NO");

      // disable row compression for the table.
      // rows inserted after this will be non-compressed.
      #sql {ALTER TABLE empl3 COMPRESS NO};
      #sql {COMMIT};
      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  INSERT \n" +
        "TO INSERT DATA INTO THE TABLE \n" +
        "\n    Perform:\n" +
        "    INSERT INTO empl3 VALUES(600, 40500)");

      // add one row of data to the table
      #sql {INSERT INTO empl3 VALUES(600, 40500)};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  ALTER TABLE \n" +
        "TO ENABLE THE COMPRESSION \n" +
        "\n    Perform:\n" +
        "    ALTER TABLE empl3 COMPRESS YES");

      // enable the row compression for the table
      #sql {ALTER TABLE empl3 COMPRESS YES};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  INSERT \n" +
        "TO INSERT DATA INTO THE TABLE \n" +
        "\n    Perform:\n" +
        "    INSERT INTO empl3 VALUES(700, 40600)");

      // add one row of data to the table
      #sql {INSERT INTO empl3 VALUES(700, 40600)};
      #sql {COMMIT};

      System.out.println(
        "\nUSE THE SQL STATEMENTS:\n" +
        "  RUNSTATS \n" +
        "TO MEASURE THE EFFECTIVENESS OF COMPRESSION \n" +
        "\n    Perform:\n" +
        "    RUNSTATS ON TABLE EMPL");

      // Perform runstats to measure the effectiveness of compression using
      // compression related catalog fields. New columns will be updated to
      // catalog table after runstats if performed on a compressed table.

      // get fully qualified name of the table
      String tableName = "EMPL3";
      String schemaName = getSchemaName(tableName);
      String fullTableName = schemaName + "." + tableName;

      // call the stored procedure ADMIN_CMD for RUNSTATS
      System.out.println();
      System.out.println("Call stored procedure named SYSPROC.ADMIN_CMD");
      str = "CALL SYSPROC.ADMIN_CMD('" +
            "RUNSTATS ON TABLE " +
            fullTableName +
            "')";

      Statement stmt1 = con.createStatement();
      stmt1.execute(str);

      stmt1.close();

      System.out.println();
      System.out.println("  SELECT * FROM empl3");
      System.out.println(
        "    EMP_NO  SALARY\n" +
        "    ------  ------");

      int emp_no = 0;
      int sal = 0;

      TbRowcompress_Cursor1 cur1;

      // declare a cursor
      #sql cur1 = {SELECT * FROM empl3};

      // fetch the cursor
      #sql {FETCH :cur1 INTO :emp_no, :sal};

      while (!cur1.endFetch())
      {
       System.out.println(
         "      " + Data.format(emp_no, 3) +
         "   " + Data.format(sal, 5));

        #sql {FETCH :cur1 INTO :emp_no, :sal};
      }

      // close the cursor
      cur1.close();

      System.out.println();
      System.out.println(
        "SELECT avgrowsize, avgcompressedrowsize, pctpagessaved,\n" +
        "       avgrowcompressionratio, pctrowscompressed\n" +
        "  FROM SYSCAT.TABLES WHERE tabname = 'EMPL3'");
      System.out.println(
        "\n    AvRowSize AvCmprsdRowSize PerPgSaved AvgRowCmprRatio" +
        " PerRowsCmprsd\n" +
        "    --------- --------------- ---------- ---------------" +
        " -------------");

      int avgrowsize = 0;
      int avgcompressedrowsize = 0;
      int pctpagessaved = 0;
      int avgrowcompressionratio = 0;
      int pctrowscompressed = 0;

      TbRowcompress_Cursor2 cur2;

      // declare a cursor
      #sql cur2 = {SELECT avgrowsize, avgcompressedrowsize,
                          pctpagessaved, avgrowcompressionratio,
                          pctrowscompressed
                     FROM SYSCAT.TABLES
                     WHERE tabname = 'EMPL3'};

      // fetch the cursor
      #sql {FETCH :cur2 INTO :avgrowsize, :avgcompressedrowsize,
                             :pctpagessaved, :avgrowcompressionratio,
                             :pctrowscompressed};

      while (!cur2.endFetch())
      {
        System.out.println(
          "    " + Data.format(avgrowsize, 4) +
          "    " + Data.format(avgcompressedrowsize, 11) +
          "    " + Data.format(pctpagessaved, 9) +
          "    " + Data.format(avgrowcompressionratio, 9) +
          "    " + Data.format(pctrowscompressed, 13));

        #sql {FETCH :cur2 INTO :avgrowsize, :avgcompressedrowsize,
                               :pctpagessaved, :avgrowcompressionratio,
                               :pctrowscompressed};
      }

      // close the cursor
      cur2.close();
    }
    catch (Exception e)
    {
      SqljException sqljExc = new SqljException(e);
      sqljExc.handle();
    }
  } // inspectCompression

  // function to get the schema name for a particular table
  static String getSchemaName(String tableName) throws Exception
  {
    // declare a cursor to run through the result of the query
    TbRowcompress_Cursor0 cur0;

    #sql cur0 = {SELECT tabschema FROM syscat.tables WHERE tabname = :tableName};

    String schemaName = null;
    #sql {FETCH :cur0 INTO :schemaName};

    // remove the trailing white space characters from schemaName before
    // returning it to the calling function
    return schemaName.trim();

  } // getSchemaName
}