/****************************************************************************
** (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: UDFcli.cs
**
** SAMPLE: Client application that calls the UDFs in UDFsrv.cs
**
*****************************************************************************
**
**  Steps to run the sample with the command line window:
**
**        I)  If you have a compatible nmake program on your system, 
**            do the following:
**
**            1. Compile this server source file, UDFsrv.cs. This 
**               erases any existing assembly of the same name and copies
**               the newly compiled assembly UDFsrv.dll from the 
**               current directory to the $(DB2PATH)\function directory). 
**               This also runs a batchfile UDFCat that executes SQL to 
**               drop (if already existing) and create the UDFs
**               in DB2 that have their UDF-body implemented by the 
**               methods in this file, UDFsrv.cs:
**
**                   nmake/make UDFsrv
**
**              To run UDFcli successfully you must have succesfully
**              completed step 1.
**
**            2. Compile the client source file UDFcli.cs.   
**
**                   nmake/make UDFcli
**
**            3. Run the client UDFcli:
**
**                   UDFcli
**
**
**        II) If you don't have a compatible nmake program on your 
**            system do the following:
**
**            1. Compile this file, UDFsrv.cs, containing the C#  
**               UDF methods that implement the UDF-bodies, with 
**               bldrn.bat. This batch file first erases any
**               existing assembly of the same name.  Then it 
**               compiles UDFsrv.cs and copies the assembly file 
**               UDFsrv.dll from the current directory to the DB2 function 
**               directory:  $(DB2PATH)\function.  
**
**                   bldrtn UDFsrv
**
**
**            2. Create the UDFs in DB2 by entering the following 
**               at the command prompt:
**
**                   UDFCat
**
**               This will drop the UDFs from DB2, if they exist, and 
**               then executes SQL CREATE FUNCTION statements to create the
**               UDFs in DB2.  These statements associate each function 
**               name with an external C# method in the assembly UDFsrv.dll 
**               that is in the DB2 function directory.
**
**            3. Compile the UDFcli.cs file with the bldapp.bat batch file by 
**               entering the following at the command prompt:
**
**                  bldapp UDFcli 
**
**            4. Run the UDFcli program by entering the program name at 
**               the command prompt:
**
**                 UDFcli
**
**  UDFcli calls several methods that call the UDFs:
**
**  Class UDFsrv contains the following methods:
**
**     1. ScalarUDF               Updates employee salary based on job type
**     2. ScratchpadScalarUDF     Increments a count each time UDF is called
**     3. ScalarUDFReturningError Shows how to return an error from a UDF 
**     4. TableUDF                Given a salary update multiplier returns a 
**                                 table-reference with employees' names, jobs, 
**                                 and updated salaries   
**
******************************************************************************
**
** SQL Statements USED:
**         SELECT
**         UPDATE
**
**                           
*****************************************************************************
**
** For more information on the sample programs, see the README file.
**
** For information on developing JDBC applications, see the Application
** Development Guide.
**
** For information on using SQL statements, see the SQL Reference. 
**
****************************************************************************/

using System;
using System.Data;
using System.IO;
using IBM.Data.DB2;

class UDFClient
{

  public static void Main(String[] args)
  {
    // Declare a DB2Connection and a DB2Transaction
    DB2Connection conn = null;
    
    DB2Transaction trans = null;  

    try
    {
      Console.WriteLine("\n  THIS SAMPLE SHOWS HOW TO CALL THE UDFs" + 
                        " IN UDFsrv.cs.\n");
      
      // Connect to a database
      Console.WriteLine("  Connecting to a database ...");
      conn = ConnectDb(args);

      // Invoke an external scalar UDF: ScalarUDF
      trans=conn.BeginTransaction();
      InvokeExternalScalarUDF(conn, trans);

      // Invoke a scalar UDF with a scratchpad: ScratchpadScUDF
      trans=conn.BeginTransaction();
      InvokeScratchpadScUDF(conn, trans);

      // Invoke a scalar UDF that returns an error: ScUDFReturningErr
      trans=conn.BeginTransaction();
      InvokeScUDFReturningErr(conn, trans);
      
      // Invoke a table UDF with a scratchpad: TableUDF
      trans=conn.BeginTransaction();
      InvokeTableUDF(conn, trans);

      // Disconnect from the database
      Console.WriteLine("\n  Disconnect from the database");
      conn.Close();

  }
    catch (Exception e)
    {
      try
      {
        Console.WriteLine("Error caught in client of UDFsrv App.");
        trans.Rollback();
        conn.Close();
      }
      catch (Exception x)
      { 
        Console.WriteLine(x.Message);
      }
      Console.WriteLine(e.Message);
    }
  }


  // This method establishes a connection to a database
  public static DB2Connection ConnectDb(String[] argv)
  {
    String server = "";
    String alias = "";
    String userId = "";
    String password = "";
    Int32 portNumber = -1;
    String connectString;

    if( argv.Length > 5 ||
        ( argv.Length == 1 &&
          ( String.Compare(argv[0],"?") == 0           ||
            String.Compare(argv[0],"-?") == 0          ||
            String.Compare(argv[0],"/?") == 0          ||
            String.Compare(argv[0],"-h",true) == 0     ||
            String.Compare(argv[0],"/h",true) == 0     ||
            String.Compare(argv[0],"-help",true) == 0  ||
            String.Compare(argv[0],"/help",true) == 0 ) ) )
    {
      throw new Exception(
        "Usage: prog_name [dbAlias] [userId passwd] \n" +
        "       prog_name [dbAlias] server portNum userId passwd");
    }

    switch (argv.Length)
    {
      case 0:  // Use all defaults
        alias = "sample";
        userId = "";
        password = "";
        break;
      case 1:  // dbAlias specified
        alias = argv[0];
        userId = "";
        password = "";
        break;
      case 2:  // userId & passwd specified
        alias = "sample";
        userId = argv[0];
        password = argv[1];
        break;
      case 3:  // dbAlias, userId & passwd specified
        alias = argv[0];
        userId = argv[1];
        password = argv[2];
        break;
      case 4:  // use default dbAlias
        alias = "sample";
        server = argv[0];
        portNumber = Convert.ToInt32(argv[1]);
        userId = argv[2];
        password = argv[3];
        break;
      case 5:  // everything specified
        alias = argv[0];
        server = argv[1];
        portNumber = Convert.ToInt32(argv[2]);
        userId = argv[3];
        password = argv[4];
        break;
    }

    if(portNumber==-1)
    {
      connectString = "Database=" + alias;
    }
    else
    {
      connectString = "Server=" + server + ":" + portNumber +
                      ";Database=" + alias;
    }
    
    if(userId != "")
    { 
      connectString += ";UID=" + userId + ";PWD=" + password;
    }

    DB2Connection conn = new DB2Connection(connectString);
    conn.Open();
    Console.WriteLine("  Connected to the " + alias + " database");
    return conn;

  } // ConnectDb

  // This method displays the result set contained in the 
  // DB2DataReader input parameter.  
  public static void DisplayResultSet(DB2DataReader reader)
  {
    while(reader.Read())
    {
      Console.Write("    ");
      for(int k=0;k<reader.FieldCount;k++)
      {
        String str;
        if (Equals(reader.GetFieldType(k),Type.GetType("System.Decimal")))
        {
          str = String.Format("{0:f2}", (Decimal)reader.GetValue(k));
        }
        else
        {
          str = reader.GetValue(k).ToString();
        }
        if(str.Length < 10)
        {
          Console.Write(str.PadRight(10,' ') + " ");
        }
        else
        {
          Console.Write(str.Substring(0,10) + " ");
        }
      }
      Console.WriteLine();
    }
  } // DisplayResultSet


  // This method invokes external scalar UDF, ScalarUDF, in a SELECT query.
  // This scalar UDF calculates and displays a new salary for employees
  // in the staff table whose name starts with 'S' based on a simple algorithm.   
  // It does not update the salaries, but displays the employee name, job,
  // salary, and the new calculated new salary.  
  //
  // UDFs are useful for transforming values that you are
  // selecting or for displaying a calculated value that is a 
  // function of the other select values or table values
  public static void InvokeExternalScalarUDF(DB2Connection conn,
                                             DB2Transaction trans)
  {
    DB2DataReader reader = null;
   
    try
    {
      Console.WriteLine("\n  Invoke the scalar UDF 'ScalarUDF' in a SELECT" + 
                        " statement:\n");

      Console.WriteLine("    SELECT name, job, salary, ");
      Console.WriteLine("           ScalarUDF(cast(job as char(5)), salary)");
      Console.WriteLine("            AS calculated_salary");
      Console.WriteLine("      FROM staff");
      Console.WriteLine("        WHERE name LIKE 'S%'");
      Console.WriteLine("\n");  

      // Create a DB2Command to execute a SELECT statement with a UDF reference
      DB2Command cmd = conn.CreateCommand();
      cmd.Transaction = trans;
      cmd.CommandText = "SELECT name, job, salary, " +
                        "       ScalarUDF(cast(job as char(5)), salary) " +
                        "        AS calculated_salary " +
                        "  FROM staff " +
                        "  WHERE name LIKE 'S%'";
      reader = cmd.ExecuteReader();

      Console.WriteLine("    NAME       JOB        SALARY     CALCULATED_SALARY");
      Console.WriteLine("    ---------- --------   ---------- -----------------");
      
      // Display the result set
      DisplayResultSet(reader);
      reader.Close();

      // Rollback the transactions to preserve the SAMPLE 
      // database's original state
      trans.Rollback();
      
    }
    catch (Exception e)
    {
      reader.Close();
      trans.Rollback();
      Console.WriteLine(e.Message);
    }
  } // InvokeScalarUDF


  // This method invokes external scratchpad scalar UDF, ScratchpadScUDF, in
  // a SELECT query.  The scratchpad scalar UDF generates a row value for each
  // row that is returned by the query - a quick way to display a numbered 
  // list to accompany row values without having to alter the table.
  public static void InvokeScratchpadScUDF(DB2Connection conn,
                                           DB2Transaction trans)
  {
    DB2DataReader reader = null;

    try
    {
      Console.WriteLine("\n\n  Invoke the scratchpad scalar UDF 'ScratchpadScUDF'"
                      + " in a SELECT statement:\n");

      Console.WriteLine("    SELECT ScratchpadScUDF() AS row_id name, job");
      Console.WriteLine("      FROM staff");
      Console.WriteLine("        WHERE name LIKE 'S%'");
      Console.WriteLine("\n");  

      // Create a DB2Command to execute a SELECT statement with a UDF reference
      DB2Command cmd = conn.CreateCommand();
      cmd.Transaction = trans;
      cmd.CommandText = "SELECT ScratchpadScUDF() AS row_id, name, job " +
                        "  FROM staff " +
                        "    WHERE name LIKE 'S%'\n";
      reader = cmd.ExecuteReader();

      Console.WriteLine("    ROW_ID     NAME       JOB");
      Console.WriteLine("    ---------- ---------- -------");
      
      // Display the result set
      DisplayResultSet(reader);
      reader.Close();

      // Rollback the transactions to preserve the SAMPLE 
      // database's original state
      trans.Rollback();
    }
    catch (Exception e)
    { 
      reader.Close();
      trans.Rollback();
      Console.WriteLine(e.Message);
    }
  } // InvokeScratchpadScUDF
  

  // This method invokes a scratchpad scalar UDF in a SELECT query to show how
  // how an error is returned from a UDF.  The UDF performs a simple division.
  // If the input divisor is 0, an error is returned.
  public static void InvokeScUDFReturningErr(DB2Connection conn,
                                             DB2Transaction trans)
  {
   
   DB2DataReader reader = null;
   
   try
    {
      Console.WriteLine("\n\n  Invoke the scalar UDF 'ScUDFReturningErr',"
                      + "  which returns an error, in a SELECT statement:\n");

      Console.WriteLine("    SELECT name, job, ScUDFReturningErr(salary, 0.0)");
      Console.WriteLine("           AS calculated_salary");
      Console.WriteLine("      FROM staff");
      Console.WriteLine("        WHERE name LIKE 'S%' ");
      Console.WriteLine("\n");  

      // Create a DB2Command to execute a SELECT statement with a UDF reference
      DB2Command cmd = conn.CreateCommand();
      cmd.Transaction = trans;
      cmd.CommandText = "SELECT ScUDFReturningErr(cast(salary as double), 0.00) " +
                        "       AS divided_salary " +
                        "  FROM staff " +
                        "    WHERE name LIKE 'S%'\n";
      reader = cmd.ExecuteReader();

      // Display the result set
      DisplayResultSet(reader);
      reader.Close();

      // Rollback the transactions to preserve the SAMPLE  
      // database's original state
      trans.Rollback();      
    }
    catch (Exception e)
    {
      reader.Close();
      trans.Rollback();
      Console.WriteLine(e.Message);
    }
  } // InvokeScUDFReturningErr


  // This method updates salaries in the EMPLOYEE table according to 3 values
  public static void InvokeTableUDF(DB2Connection conn,
                                    DB2Transaction trans)
  {
    DB2DataReader reader = null;
    
    try
    {
      Console.WriteLine("\n\n  Invoke the tableUDF 'TableUDF',"
                      + "  in a SELECT statement:\n");

      Console.WriteLine("    SELECT udfTable.name, udfTable.job,");
      Console.WriteLine("           udfTable.salary");
      Console.WriteLine("      FROM TABLE(TableUDF(1.5))");
      Console.WriteLine("        AS udfTable");
      Console.WriteLine("\n");  

      // Create a DB2Command to execute a SELECT statement with 
      // a UDF table-reference (note the use of the keyword TABLE)
      DB2Command cmd = conn.CreateCommand();
      cmd.Transaction = trans;
      cmd.CommandText = "SELECT udfTable.name, udfTable.job, udfTable.salary" +
                        "  FROM TABLE(TableUDF(1.5))" +
                        "    AS udfTable\n";
      reader = cmd.ExecuteReader();


      Console.WriteLine("    NAME       JOB        SALARY");
      Console.WriteLine("    ---------- ---------- -------");

      // Display the result set
      DisplayResultSet(reader);
      reader.Close();

      // Rollback the transactions to preserve the SAMPLE  
      // database's original state
      trans.Rollback();
    }
    catch (Exception e)
    {
      reader.Close();
      trans.Rollback();
      Console.WriteLine(e.Message);
    }
  } // InvokeTableUDF

} //UDFClient