//
// (C) Copyright 2009 EffiProz Systems
// All rights reserved.

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using EffiProz.Core.Engine;
using EffiProz.Core.Results;
using EffiProz.Core;
using EffiProz.Core.Lib;
using EffiProz.Core.Navigators;
using EffiProz.Core.DataTypes;


namespace SelfTestSL
{
    /**
  * Utility class providing methodes for submitting test statements or
  * scripts to the database, comparing the results returned with
  * the expected results. The test script format is compatible with existing
  * scripts.
  *
  * @author ewanslater@users
  * @author fredt@users
  */
    public class TestUtil
    {

        /**
         * Runs a preformatted script.<p>
         *
         * Where a result set is required, each line in the script will
         * be interpreted as a seperate expected row in the ResultSet
         * returned by the query.  Within each row, fields should be delimited
         * using either comma (the default), or a user defined delimiter
         * which should be specified in the System property TestUtilFieldDelimiter
         * @param aConnection Connection object for the database
         * @param aPath Path of the script file to be tested
         */

        public static bool testScript(SessionInterface aConnection, String aPath)
        {

            try
            {
                SessionInterface statement = aConnection;
#if !SILVERLIGHT
                FileInfo testfile = new FileInfo(aPath);
                StreamReader reader = testfile.OpenText();
#else
                //string fullPath = "SelfTestSL.Scripts."+aPath.Substring(aPath.LastIndexOf("/")+1);
                Stream stream = Assembly.GetCallingAssembly().GetManifestResourceStream(aPath);
                StreamReader reader = new StreamReader(stream);
#endif

                List<string> section = null;

                print("Opened test script file: " + aPath);

                /**
                 * we read the lines from the start of one section of the script "/*"
                 *  until the start of the next section, collecting the lines
                 *  in the Vector lines.
                 *  When a new section starts, we will pass the vector of lines
                 *  to the test method to be processed.
                 */
                int startLine = 0;
                int currentLine = 0;

                while (true)
                {
                    bool startSection = false;
                    String line = reader.ReadLine();
                    currentLine++;

                    if (line == null)
                    {
                        break;
                    }

                    line = line.Substring(0, StringUtil.RTrimSize(line));

                    //if the line is blank or a comment, then ignore it
                    if ((line.Length == 0) || line.StartsWith("--"))
                    {
                        continue;
                    }

                    //...check if we're starting a new section...
                    if (line.StartsWith("/*"))
                    {
                        startSection = true;
                    }

                    if (line[0] != ' ' && line[0] != '*')
                    {
                        startSection = true;
                    }

                    if (startSection)
                    {

                        //...if we are, test the previous section (if it exists)...
                        if (section != null)
                        {
                            testSection(statement, section, startLine);
                        }

                        //...and then start a new section...
                        section = new List<string>();
                        startLine = currentLine;
                    }

                    section.Add(line);
                }

                //send the last section for testing
                if (section != null)
                {
                    testSection(statement, section, startLine);
                }

                //statement.Close();
                print("Processed lines: " + startLine);
            }
            catch (Exception e)
            {
                //print(e.StackTrace);
                return false;
                //Assert.Fail("test script file error: " + e.Message + " " + e.StackTrace);
            }

            return true;
        }

        /**
         * Performs a preformatted statement or group of statements and throws
         *  if the result does not match the expected one.
         * @param line start line in the script file for this test
         * @param stat Statement object used to access the database
         * @param s Contains the type, expected result and SQL for the test
         */
        static void test(SessionInterface stat, String s, int line)
        {

            //maintain the interface for this method
            List<string> section = new List<string>();

            section.Add(s);
            testSection(stat, section, line);
        }

        /**
         * Method to save typing ;-)
         * @param s String to be printed
         */
        static void print(String s)
        {
            System.Diagnostics.Debug.WriteLine(s);
#if !SILVERLIGHT
            System.Diagnostics.Debug.Flush();
#endif

        }

        /**
         * Takes a discrete section of the test script, contained in the
         * section vector, splits this into the expected result(s) and
         * submits the statement to the database, comparing the results
         * returned with the expected results.
         * If the actual result differs from that expected, or an
         * exception is thrown, then the appropriate message is printed.
         * @param stat Statement object used to access the database
         * @param section Vector of script lines containing a discrete
         * section of script (i.e. test type, expected results,
         * SQL for the statement).
         * @param line line of the script file where this section started
         */
        private static void testSection(SessionInterface stat, List<string> section,
                                        int line)
        {

            //create an appropriate instance of ParsedSection
            ParsedSection pSection = parsedSectionFactory(section);

            if (pSection == null)
            {    //it was not possible to sucessfully parse the section
                throw new Exception("The section starting at line " + line
                      + " could not be parsed, " + "and so was not processed.\n");
            }
            else if (pSection is IgnoreParsedSection)
            {
                print("Line " + line + ": " + pSection.getResultString());
            }
            else if (pSection is DisplaySection)
            {

                // May or may not want to report line number for 'd' sections.  ?
                print(pSection.getResultString());
            }
            else if (!pSection.test(stat))
            {
                print("section starting at line " + line);
                print("returned an unexpected result:");
                print(pSection.ToString());
               throw new Exception("section starting at line " + line + "returned an unexpected result:" + pSection.ToString());
            }
        }

        /**
         * Factory method to create appropriate parsed section class for the section
         * @param aSection Vector containing the section of script
         * @return a ParesedSection object
         */
        private static ParsedSection parsedSectionFactory(
                List<string> aSection)
        {

            //type of the section
            char type = ' ';

            //section represented as an array of Strings, one for each significant
            //line in the section
            String[] rows = null;

            //read the first line of the Vector...
            String topLine = aSection[0];

            //...and check it for the type...
            if (topLine.StartsWith("/*"))
            {
                type = topLine[2];

                //if the type code is invalid return null
                if (!ParsedSection.isValidCode(type))
                {
                    return null;
                }

                //if the type code is UPPERCASE and system property IgnoreCodeCase
                //has been set to true, make the type code lowercase
                if (Char.IsUpper(type))
                {
                    type = char.ToLower(type);
                }

                //...strip out the type declaration...
                topLine = topLine.Substring(3);
            }

            //if, after stripping out the declaration from topLine, the length of topLine
            //is greater than 0, then keep the rest of the line, as the first row.
            //Otherwise it will be discarded, and the offset (between the array and the vector)
            //set to 1.
            int offset = 0;

            if (topLine.Trim().Length > 0)
            {
                rows = new String[aSection.Count];
                rows[0] = topLine;
            }
            else
            {
                rows = new String[aSection.Count - 1];
                offset = 1;
            }

            //pull the rest of aSection into the rows array.
            for (int i = (1 - offset); i < rows.Length; i++)
            {
                rows[i] = (String)aSection[i + offset];
            }

            //then pass this to the constructor for the ParsedSection class that
            //corresponds to the value of type
            switch (type)
            {

                case 'u':
                    return new UpdateParsedSection(rows);

                case 's':
                    return new SilentParsedSection(rows);

                case 'r':
                    return new ResultSetParsedSection(rows);

                case 'a':
                    return new ResultSetParsedSectionUnOrdered(rows);

                case 'c':
                    return new CountParsedSection(rows);

                case 'd':
                    return new DisplaySection(rows);

                case 'e':
                    return new ExceptionParsedSection(rows);

                case ' ':
                    return new BlankParsedSection(rows);

                default:

                    //if we arrive here, then we should have a valid code,
                    //since we validated it earlier, so return an
                    //IgnoreParsedSection object
                    return new IgnoreParsedSection(rows, type);
            }
        }
    }

    /**
     * Abstract inner class representing a parsed section of script.
     * The specific ParsedSections for each type of test should inherit from this.
     */
    abstract class ParsedSection
    {

        /**
         * Type of this test.
         * @see isValidCase() for allowed values
         */
        protected char type = ' ';

        /** error message for this section */
        public String message = null;

        /** contents of the section as an array of Strings, one for each line in the section. */
        protected String[] lines = null;

        /** number of the last row containing results in sectionLines */
        protected int resEndRow = 0;

        /** SQL query to be submitted to the database. */
        protected String sqlString = null;

        /**
         * Constructor when the section's input lines do not need to be parsed
         * into SQL.
         */
        protected ParsedSection() { }

        /**
         * Common constructor functions for this family.
         * @param aLines Array of the script lines containing the section of script.
         * database
         */
        protected ParsedSection(String[] aLines)
        {

            lines = aLines;

            //read the lines array backwards to get out the SQL String
            //using a StringBuilder for efficency until we've got the whole String
            StringBuilder sqlBuff = new StringBuilder();
            int endIndex = 0;
            int k = lines.Length - 1;

            do
            {

                //check to see if the row contains the end of the result set
                if ((endIndex = lines[k].IndexOf("*/")) != -1)
                {

                    //then this is the end of the result set
                    sqlBuff.Insert(0, lines[k].Substring(endIndex + 2));

                    lines[k] = lines[k].Substring(0, endIndex);

                    if (lines[k].Length == 0)
                    {
                        resEndRow = k - 1;
                    }
                    else
                    {
                        resEndRow = k;
                    }

                    break;
                }
                else
                {
                    sqlBuff.Insert(0, lines[k]);
                }

                k--;
            } while (k >= 0);

            //set sqlString value
            sqlString = sqlBuff.ToString();
        }

        /**
         * String representation of this ParsedSection
         * @return String representation of this ParsedSection
         */
        public override String ToString()
        {

            StringBuilder b = new StringBuilder();

            b.Append("\n******\n");
            b.Append("contents of lines array:\n");

            for (int i = 0; i < lines.Length; i++)
            {
                if (lines[i].Trim().Length > 0)
                {
                    b.Append("line ").Append(i).Append(": ").Append(
                        lines[i]).Append("\n");
                }
            }

            b.Append("Type: ");
            b.Append(getType()).Append("\n");
            b.Append("SQL: ").Append(getSql()).Append("\n");
            b.Append("results:\n");
            b.Append(getResultString());

            //check to see if the message field has been populated
            if (getMessage() != null)
            {
                b.Append("\nmessage:\n");
                b.Append(getMessage());
            }

            b.Append("\n******\n");

            return b.ToString();
        }

        /**
         * returns a String representation of the expected result for the test
         * @return The expected result(s) for the test
         */
        internal abstract String getResultString();

        /**
         *  returns the error message for the section
         *
         * @return message
         */
        protected String getMessage()
        {
            return message;
        }

        /**
         * returns the type of this section
         * @return type of this section
         */
        protected char getType()
        {
            return type;
        }

        /**
         * returns the SQL statement for this section
         * @return SQL statement for this section
         */
        protected String getSql()
        {
            return sqlString;
        }

        internal Result prepare(SessionInterface aStatement, string sql)
        {
            Result resultOut = Result.NewPrepareStatementRequest();
            resultOut.SetMainString(sql);

            resultOut.SetPrepareOrExecuteProperties(sql, 0, 0, 0, 0,
               ResultProperties.defaultPropsValue, ResultConstants.RETURN_NO_GENERATED_KEYS
               , null, null);

            
            Result resultin = aStatement.Execute(resultOut);

            if (resultin.IsError())
            {
                throw resultin.GetException();
            }

            long statementID = resultin.GetStatementId();          

            resultOut = Result.NewPreparedExecuteRequest(resultin.parameterMetaData.GetParameterTypes(),
                statementID);
            resultOut.SetStatement(resultin.GetStatement());

            return resultOut;

        }

        /**
         * performs the test contained in the section against the database.
         * @param aStatement Statement object
         * @return true if the result(s) are as expected, otherwise false
         */
        internal virtual bool test(SessionInterface aStatement)
        {

            Result resultOut = Result.NewExecuteDirectRequest();
            resultOut.SetMainString(getSql());
            resultOut.SetMaxRows(1000);

            Result resultIn = null;

            try
            {
                resultIn = aStatement.Execute(resultOut);
            }
            catch (Exception x)
            {
                message = x.Message;

                return false;
            }
            if (resultIn.IsError())
            {
                if (resultIn.GetException() != null)
                    message = resultIn.GetMainString() + "\n " + resultIn.GetException().Message + "\n " + resultIn.GetException().StackTrace;
                else
                    message = resultIn.GetMainString();
                return false;
            }

            return true;
        }

        /**
         * Checks that the type code letter is valid
         * @param aCode type code to validate.
         * @return true if the type code is valid, otherwise false.
         */
        internal static bool isValidCode(char aCode)
        {

            /* Allowed values for test codes are:
             * (note that UPPERCASE codes, while valid are only processed if the
             * system property IgnoreCodeCase has been set to true)
             *
             * 'u' ('U') - update
             * 'c' ('C') - count
             * 'e' ('E') - exception
             * 'r' ('R') - results
             * 's' ('S') - silent
             * 'd'       - display   (No reason to use upper-case).
             * ' ' - not a test
             */
            char testChar = Char.ToLower(aCode);

            switch (testChar)
            {

                case ' ':
                case 'r':
                case 'e':
                case 'c':
                case 'u':
                case 's':
                case 'd':
                case 'a':
                    return true;
            }

            return false;
        }
    }

    class ResultSetParsedSectionUnOrdered : ParsedSection
    {

        private String delim = ",";
        private String[] expectedRows = null;

        /**
         * constructs a new instance of ResultSetParsedSection, interpreting
         * the supplied results as one or more lines of delimited field values
         * @param lines String[]
         */
        internal ResultSetParsedSectionUnOrdered(String[] lines)
            : base(lines)
        {



            type = 'a';

            //now we'll populate the expectedResults array
            expectedRows = new String[(resEndRow + 1)];

            for (int i = 0; i <= resEndRow; i++)
            {
                int skip = StringUtil.SkipSpaces(lines[i], 0);

                expectedRows[i] = lines[i].Substring(skip);
            }
        }

        internal override String getResultString()
        {

            StringBuilder printVal = new StringBuilder();

            for (int i = 0; i < getExpectedRows().Length; i++)
            {
                printVal.Append(getExpectedRows()[i]).Append("\n");
            }

            return printVal.ToString();
        }

        internal override bool test(SessionInterface aStatement)
        {

            Result resultOut = Result.NewExecuteDirectRequest();
            resultOut.SetMainString(getSql());
            resultOut.SetMaxRows(1000);

            Result resultIn = null;

            try
            {
                try
                {
                    //execute the SQL
                    resultIn = aStatement.Execute(resultOut);
                }
                catch (Exception s)
                {
                    throw new Exception(
                        "Expected a ResultSet, but got the error: "
                        + s.Message);
                }
                if (resultIn.IsError())
                {
                    throw new Exception(resultIn.GetMainString() + "\n " + resultIn.GetException().Message +
                        "\n " + resultIn.GetException().StackTrace);

                }

                //check that update count != 0
                if (resultIn.GetUpdateCount() != 0)
                {
                    throw new Exception(
                        "Expected a ResultSet, but got an update count of "
                        + resultIn.GetUpdateCount());
                }

                //iterate over the ResultSet
                RowSetNavigator it = resultIn.GetNavigator();

                
                object[] result = null;
                while (it.HasNext())
                {
                    int count = 0;
                    result = (object[])it.GetNext();
                    while (count < getExpectedRows().Length)
                    {

                        //                    String[] expectedFields = getExpectedRows()[count].split(delim);
                        String[] expectedFields =
                            StringUtil.Split(getExpectedRows()[count], delim);

                        //check that we have the number of columns expected...
                        if (resultIn.metaData.GetColumnCount()
                                == expectedFields.Length)
                        {

                            //...and if so, check that the column values are as expected...
                            int j = 0;
                            bool matched = true;
                            for (int i = 0; i < expectedFields.Length; i++)
                            {
                                j = i;



                                //...including null values...
                                if (result[j] == null)
                                {    //..then we have a null

                                    //...check to see if we were expecting it...
                                    if (!expectedFields[i].Equals(
                                            "NULL", StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        matched = false;
                                    }
                                }
                                else
                                {
                                    if (result[j] is double)
                                    {
                                        double val1 = double.Parse(expectedFields[i]);
                                        double val2 = (double)result[j];
                                        if (val1 != val2)
                                            matched = false;

                                    }
                                    else if (result[j] is decimal)
                                    {
                                        decimal val1 = decimal.Parse(expectedFields[i]);
                                        decimal val2 = (decimal)result[j];
                                        if (val1 != val2)
                                            matched = false;

                                    }
                                    else if (result[j] is DateTime)
                                    {
                                        DateTime val1 = DateTime.Parse(expectedFields[i]);
                                        DateTime val2 = (DateTime)result[j];
                                        if (val1 != val2)
                                            matched = false;

                                    }
                                    else if (result[j] is IntervalMonthData)
                                    {

                                        IntervalMonthData md = (IntervalMonthData)result[j];
                                        long val1 = Convert.ToInt64(expectedFields[i]);
                                        long val2 = md.units;
                                        if (val1 != val2)
                                            matched = false;

                                    }
                                    else if (result[j] is IntervalSecondData)
                                    {
                                        IntervalSecondData sd = (IntervalSecondData)result[j];
                                        TimeSpan val1 = TimeSpan.Parse(expectedFields[i]);
                                        long val2 = sd.units;
                                        double totalsecs = sd.units + sd.nanos / 1E9;
                                        if (Math.Abs(totalsecs - val1.TotalSeconds) > 0.000001)
                                            matched = false;

                                    }
                                    else if (result[j] is TimestampData)
                                    {
                                        DateTime val1 = DateTime.Parse(expectedFields[i]);
                                        TimestampData td = (TimestampData)result[j];
                                        DateTime val2 = new DateTime(td.GetSeconds() * TimeSpan.TicksPerSecond +
                                            td.GetNanos() / 100);
                                        if (val1 != val2)
                                            matched = false;

                                    }
                                    else if (result[j] is BinaryData)
                                    {
                                        String val1 = (string)(expectedFields[i]);
                                        BinaryData bd = (BinaryData)result[j];

                                        try
                                        {
                                            if (bd.IsBits())
                                            {
                                                if (bd.BitLength(aStatement) == val1.Length)
                                                {
                                                    string bitVal = "";
                                                    byte[] bytes = bd.GetBytes();
                                                    for (int ibyte = 0; ibyte < bytes.Length; ibyte++)
                                                    {
                                                        byte mask = 1;
                                                        for (int ii = 0; ii < 8; ii++)
                                                        {
                                                            mask = (byte)(1 << ii);
                                                            if ((bytes[ibyte] & mask) == 1)
                                                                bitVal = 1 + bitVal;
                                                            else
                                                                bitVal = 0 + bitVal;
                                                        }
                                                    }

                                                    if (bitVal != val1)
                                                        matched = false; 
                                                }
                                                else
                                                    matched = false; 

                                            }
                                            else
                                            {
                                                byte[] bytes = bd.GetBytes();
                                                for (int ii = 0; ii < bd.Length(aStatement); ii++)
                                                {
                                                    if (bytes[ii] != byte.Parse((val1.Substring(val1.Length - 2 - ii * 2, 2)), System.Globalization.NumberStyles.HexNumber))
                                                        matched = false;
                                                }
                                            }
                                        }
                                        catch (Exception)
                                        {
                                            matched = false;
                                        }

                                        //DateTime val2 = new DateTime(td.getSeconds() * TimeSpan.TicksPerSecond +
                                        //    td.getNanos() / 100);
                                        //if (val1 != val2)
                                        //    throw new Exception(
                                        //  "Expected (DateTime) row " + (count + 1)
                                        //  + " of the ResultSet to contain:\n"
                                        //  + val1
                                        //  + "\nbut field " + j + " contained "
                                        //  + val2);

                                    }
                                    else
                                    {
                                        String actual = result[j] == null ? null : Convert.ToString(result[j]);
                                        string expected = expectedFields[i];

                                        if (!actual.Equals(expected))
                                        {

                                            matched = false;
                                        }
                                    }
                                }
                            }

                            if (matched)
                                break;
                        }
                        else
                        {

                            //we have the wrong number of columns
                            throw new Exception(
                                "Expected the ResultSet to contain "
                                + expectedFields.Length
                                + " fields, but it contained "
                                + resultIn.metaData.GetColumnCount()
                                + " fields.");
                        }
                    }

                    count++;
                }               
            }
            catch (Exception x)
            {
                message = x.Message;

                return false;
            }

            return true;
        }

        private String[] getExpectedRows()
        {
            return expectedRows;
        }
    }

    /** Represents a ParsedSection for a ResultSet test */
    class ResultSetParsedSection : ParsedSection
    {

        private String delim = ",";
        private String[] expectedRows = null;

        /**
         * constructs a new instance of ResultSetParsedSection, interpreting
         * the supplied results as one or more lines of delimited field values
         * @param lines String[]
         */
        internal ResultSetParsedSection(String[] lines)
            : base(lines)
        {



            type = 'r';

            //now we'll populate the expectedResults array
            expectedRows = new String[(resEndRow + 1)];

            for (int i = 0; i <= resEndRow; i++)
            {
                int skip = StringUtil.SkipSpaces(lines[i], 0);

                expectedRows[i] = lines[i].Substring(skip);
            }
        }

        internal override String getResultString()
        {

            StringBuilder printVal = new StringBuilder();

            for (int i = 0; i < getExpectedRows().Length; i++)
            {
                printVal.Append(getExpectedRows()[i]).Append("\n");
            }

            return printVal.ToString();
        }

        internal override bool test(SessionInterface aStatement)
        {

            Result resultOut = Result.NewExecuteDirectRequest();
            resultOut.SetMainString(getSql());
            resultOut.SetMaxRows(1000);           

            Result resultIn = null;
           // Result resultOut = null;
         
            //resultOut = prepare(aStatement, getSql());
           
            try
            {
                try
                {                    
                    //execute the SQL
                    resultIn = aStatement.Execute(resultOut);
                   
                }
                catch (Exception s)
                {
                    throw new Exception(
                        "Expected a ResultSet, but got the error: "
                        + s.Message + "Stack: " + s.StackTrace);
                }
                if (resultIn.IsError())
                {
                    throw new Exception(resultIn.GetMainString() + "\n " + resultIn.GetException().Message +
                        "\n " + resultIn.GetException().StackTrace);

                }

                //check that update count != 0
                if (resultIn.GetUpdateCount() != 0)
                {
                    throw new Exception(
                        "Expected a ResultSet, but got an update count of "
                        + resultIn.GetUpdateCount());
                }

                //iterate over the ResultSet
                RowSetNavigator it = resultIn.GetNavigator();

                int count = 0;
                object[] result = null;
                while (it.HasNext())
                {
                    result = (object[])it.GetNext();
                    if (count < getExpectedRows().Length)
                    {

                        //                    String[] expectedFields = getExpectedRows()[count].split(delim);
                        String[] expectedFields =
                            StringUtil.Split(getExpectedRows()[count], delim);

                        //check that we have the number of columns expected...
                        if (resultIn.metaData.GetColumnCount()
                                == expectedFields.Length)
                        {

                            //...and if so, check that the column values are as expected...
                            int j = 0;

                            for (int i = 0; i < expectedFields.Length; i++)
                            {
                                j = i;



                                //...including null values...
                                if (result[j] == null)
                                {    //..then we have a null

                                    //...check to see if we were expecting it...
                                    if (!expectedFields[i].Equals(
                                            "NULL", StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        throw new Exception(
                                            "Expected row " + count
                                            + " of the ResultSet to contain:\n"
                                            + getExpectedRows()[count]
                                            + "\nbut field " + j
                                            + " contained NULL");
                                    }
                                }
                                else
                                {
                                    if (result[j] is double)
                                    {
                                        double val1 = double.Parse(expectedFields[i]);
                                        double val2 = (double)result[j];
                                        if (Math.Abs(val1 - val2) > 0.00001)
                                            throw new Exception(
                                           "Expected (double) row " + (count + 1)
                                           + " of the ResultSet to contain:\n"
                                           + val1
                                           + "\nbut field " + j + " contained "
                                           + val2);

                                    }
                                    else if (result[j] is decimal)
                                    {
                                        decimal val1 = decimal.Parse(expectedFields[i]);
                                        decimal val2 = (decimal)result[j];
                                        if (val1 != val2)
                                            throw new Exception(
                                          "Expected (double) row " + (count + 1)
                                          + " of the ResultSet to contain:\n"
                                          + val1
                                          + "\nbut field " + j + " contained "
                                          + val2);

                                    }
                                    else if (result[j] is DateTime)
                                    {
                                        DateTime val1 = DateTime.Parse(expectedFields[i]);
                                        DateTime val2 = (DateTime)result[j];
                                        if (val1 != val2)
                                            throw new Exception(
                                          "Expected (DateTime) row " + (count + 1)
                                          + " of the ResultSet to contain:\n"
                                          + val1
                                          + "\nbut field " + j + " contained "
                                          + val2);

                                    }
                                    else if (result[j] is IntervalMonthData)
                                    {
                                       
                                        IntervalMonthData md = (IntervalMonthData)result[j];
                                        long val1 = Convert.ToInt64(expectedFields[i]);
                                        long val2 = md.units;
                                        if (val1 != val2)
                                            throw new Exception(
                                          "Expected (IntervalMonthData) row " + (count + 1)
                                          + " of the ResultSet to contain:\n"
                                          + val1
                                          + "\nbut field " + j + " contained "
                                          + val2);

                                    }
                                    else if (result[j] is IntervalSecondData)
                                    {
                                        IntervalSecondData sd = (IntervalSecondData)result[j];
                                        TimeSpan val1 = TimeSpan.Parse(expectedFields[i]);
                                        long val2 = sd.units;
                                        double totalsecs = sd.units + sd.nanos / 1E9;
                                        if (Math.Abs(totalsecs - val1.TotalSeconds) > 0.000001)
                                            throw new Exception(
                                          "Expected (IntervalSecondData) row " + (count + 1)
                                          + " of the ResultSet to contain:\n"
                                          + val1
                                          + "\nbut field " + j + " contained "
                                          + val2);

                                    }
                                    else if (result[j] is TimestampData)
                                    {
                                        DateTime val1 = DateTime.Parse(expectedFields[i]);
                                        TimestampData td = (TimestampData)result[j];
                                        DateTime val2 = new DateTime(td.GetSeconds() * TimeSpan.TicksPerSecond +
                                            td.GetNanos() / 100);
                                        if (val1 != val2)
                                            throw new Exception(
                                          "Expected (DateTime) row " + (count + 1)
                                          + " of the ResultSet to contain:\n"
                                          + val1
                                          + "\nbut field " + j + " contained "
                                          + val2);

                                    }
                                    else if (result[j] is ClobDataID)
                                    {
                                        String val1 = (string)(expectedFields[i]);
                                        ClobDataID cd = (ClobDataID)result[j];
                                        long len = cd.Length(aStatement);
                                        char[] chars = cd.GetChars(aStatement, 0, (int)len);
                                        string val2 = new String(chars);

                                        if(val1.Length != len)
                                            throw new Exception(
                                        "Expected (Clob) row " + (count + 1)
                                        + " of the ResultSet to contain:\n"
                                        + val1
                                        + "\nbut field " + j + " contained "
                                        + val2);
                                    }
                                    else if (result[j] is BinaryData)
                                    {
                                        String val1 = (string)(expectedFields[i]);
                                        BinaryData bd = (BinaryData)result[j];

                                        try
                                        {
                                            if (bd.IsBits())
                                            {
                                                if (bd.BitLength(aStatement) == val1.Length)
                                                {
                                                    string bitVal = "";
                                                    byte[] bytes = bd.GetBytes();
                                                    for (int ibyte = 0; ibyte < bytes.Length; ibyte++)
                                                    {
                                                        byte mask = 1;
                                                        for (int ii = 0; ii < 8; ii++)
                                                        {
                                                            mask = (byte)(1 << ii);
                                                            if ((bytes[ibyte] & mask) == 1)
                                                                bitVal = 1 + bitVal;
                                                            else
                                                                bitVal = 0 + bitVal;
                                                        }
                                                    }

                                                    if (bitVal != val1)
                                                        throw new Exception();
                                                }
                                                else
                                                    throw new Exception();

                                            }
                                            else
                                            {
                                                byte[] bytes = bd.GetBytes();
                                                for (int ii = 0; ii < bd.Length(aStatement); ii++)
                                                {
                                                    if (bytes[ii] != byte.Parse((val1.Substring(val1.Length - 2 - ii * 2, 2)), System.Globalization.NumberStyles.HexNumber))
                                                        throw new Exception();
                                                }
                                            }
                                        }
                                        catch (Exception)
                                        {
                                            throw new Exception(
                                          "Expected (Binary) row " + (count + 1)
                                          + " of the ResultSet to contain:\n"
                                          + val1
                                          + "\nbut field " + j + " contained "
                                          + bd.GetBytes().ToString());
                                        }

                                        //DateTime val2 = new DateTime(td.getSeconds() * TimeSpan.TicksPerSecond +
                                        //    td.getNanos() / 100);
                                        //if (val1 != val2)
                                        //    throw new Exception(
                                        //  "Expected (DateTime) row " + (count + 1)
                                        //  + " of the ResultSet to contain:\n"
                                        //  + val1
                                        //  + "\nbut field " + j + " contained "
                                        //  + val2);

                                    }
                                    else
                                    {
                                        String actual = result[j] == null ? null : Convert.ToString(result[j]);
                                        string expected = expectedFields[i];

                                        if (!actual.Equals(expected))
                                        {

                                            //then the results are different
                                            throw new Exception(
                                                "Expected row " + (count + 1)
                                                + " of the ResultSet to contain:\n"
                                                + getExpectedRows()[count]
                                                + "\nbut field " + j + " contained "
                                                + result[j]);
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {

                            //we have the wrong number of columns
                            throw new Exception(
                                "Expected the ResultSet to contain "
                                + expectedFields.Length
                                + " fields, but it contained "
                                + resultIn.metaData.GetColumnCount()
                                + " fields.");
                        }
                    }

                    count++;
                }

                //check that we got as many rows as expected
                if (count != getExpectedRows().Length)
                {

                    //we don't have the expected number of rows
                    throw new Exception("Expected the ResultSet to contain "
                                        + getExpectedRows().Length
                                        + " rows, but it contained " + count
                                        + " rows.");
                }
            }
            catch (Exception x)
            {
                message = x.Message;

                return false;
            }

            return true;
        }

        private String[] getExpectedRows()
        {
            return expectedRows;
        }
    }

    /** Represents a ParsedSection for an update test */
    class UpdateParsedSection : ParsedSection
    {

        //expected update count
        int countWeWant;

        internal UpdateParsedSection(String[] lines)
            : base(lines)
        {



            type = 'u';
            countWeWant = int.Parse(lines[0]);
        }

        internal override String getResultString()
        {
            return getCountWeWant().ToString();
        }

        private int getCountWeWant()
        {
            return countWeWant;
        }

        internal override bool test(SessionInterface aStatement)
        {

            Result resultOut = Result.NewExecuteDirectRequest();
            resultOut.SetMainString(getSql());
            resultOut.SetMaxRows(1000);

            Result resultIn = null;

            try
            {
                try
                {

                    //execute the SQL
                    resultIn = aStatement.Execute(resultOut);
                }
                catch (Exception s)
                {
                    throw new Exception("Expected an update count of "
                                        + getCountWeWant()
                                        + ", but got the error: "
                                        + s.Message + " ");
                }
                if (resultIn.IsError())
                {
                    throw new Exception(resultIn.GetMainString() + "\n " + resultIn.GetException().Message +
                                        "\n " + resultIn.GetException().StackTrace);
                }

                if (resultIn.GetUpdateCount() != getCountWeWant())
                {
                    throw new Exception("Expected an update count of "
                                        + getCountWeWant()
                                        + ", but got an update count of "
                                        + resultIn.GetUpdateCount() + ". ");
                }
            }
            catch (Exception x)
            {
                message = x.Message;

                return false;
            }

            return true;
        }
    }

    /** Represents a ParsedSection for silent execution */
    class SilentParsedSection : ParsedSection
    {

        internal SilentParsedSection(String[] lines)
            : base(lines)
        {



            type = 's';
        }

        internal override String getResultString()
        {
            return null;
        }

        internal override bool test(SessionInterface aStatement)
        {

            Result resultOut = Result.NewExecuteDirectRequest();
            resultOut.SetMainString(getSql());
            resultOut.SetMaxRows(1000);

            Result resultIn = null;

            try
            {
                resultIn = aStatement.Execute(resultOut);
            }
            catch (Exception) { }

            //if (resultIn.isError())
            //{
            //    Assert.Fail(resultIn.getMainString() + "\n " + resultIn.getException().Message + "\n " + resultIn.getException().StackTrace);
            //}

            return true;
        }
    }

    /** Represents a ParsedSection for a count test */
    class CountParsedSection : ParsedSection
    {

        //expected row count
        private int countWeWant;

        internal CountParsedSection(String[] lines)
            : base(lines)
        {



            type = 'c';
            countWeWant = int.Parse(lines[0]);
        }

        internal override String getResultString()
        {
            return getCountWeWant().ToString();
        }

        private int getCountWeWant()
        {
            return countWeWant;
        }

        internal override bool test(SessionInterface aStatement)
        {

            Result resultOut = Result.NewExecuteDirectRequest();
            resultOut.SetMainString(getSql());
            resultOut.SetMaxRows(1000);

            Result resultIn = null;
            try
            {

                //execute the SQL
                try
                {
                    resultIn = aStatement.Execute(resultOut);
                }
                catch (Exception s)
                {
                    throw new Exception("Expected a ResultSet containing "
                                        + getCountWeWant()
                                        + " rows, but got the error: "
                                        + s.Message);
                }
                if (resultIn.IsError())
                {
                    throw new Exception(resultIn.GetMainString() + "\n " + resultIn.GetException().Message +
                                        "\n " + resultIn.GetException().StackTrace);
                }

                //check that update count != 0
                if (resultIn.GetUpdateCount() != 0)
                {
                    throw new Exception(
                        "Expected a ResultSet, but got an update count of "
                        + resultIn.GetUpdateCount());
                }

                //iterate over the ResultSet
                RowSetNavigator it = resultIn.GetNavigator();

                int count = 0;

                while (it.HasNext())
                {
                    it.Next();
                    count++;
                    it.GetCurrent();
                }


                //check whether reset navigator works
                it.Reset();
                count = 0;
                while (it.HasNext())
                {
                    it.Next();
                    count++;
                    it.GetCurrent();
                }

                //check that we got as many rows as expected
                if (count != getCountWeWant())
                {

                    //we don't have the expected number of rows
                    throw new Exception("Expected the ResultSet to contain "
                                        + getCountWeWant()
                                        + " rows, but it contained " + count
                                        + " rows.");
                }


                //check that we got as many rows as expected
                if (count != getCountWeWant())
                {

                    //we don't have the expected number of rows
                    throw new Exception("Expected the ResultSet to contain "
                                        + getCountWeWant()
                                        + " rows, but it contained " + count
                                        + " rows.");
                }

            }
            catch (Exception x)
            {
                message = x.Message;

                return false;
            }

            return true;
        }
    }

    /** Represents a ParsedSection for an Exception test */
    class ExceptionParsedSection : ParsedSection
    {

        internal ExceptionParsedSection(String[] lines)
            : base(lines)
        {
            type = 'e';
        }

        internal override String getResultString()
        {
            return "Exception";
        }

        internal override bool test(SessionInterface aStatement)
        {

            Result resultOut = Result.NewExecuteDirectRequest();
            resultOut.SetMainString(getSql());
            resultOut.SetMaxRows(1000);

            Result resultIn = null;
            try
            {
                resultIn = aStatement.Execute(resultOut);
            }
            catch (Exception x)
            {
                message = x.Message;

                return false;
            }

            if (resultIn.IsError())
            {
                return true;
            }

            return false;
        }
    }

    /** Represents a ParsedSection for a section with blank type */
    class BlankParsedSection : ParsedSection
    {

        internal BlankParsedSection(String[] lines)
            : base(lines)
        {


            type = ' ';
        }

        internal override String getResultString()
        {
            return "No result specified for this section";
        }
    }

    /** Represents a ParsedSection that is to be ignored */
    class IgnoreParsedSection : ParsedSection
    {

        internal IgnoreParsedSection(String[] inLines, char aType)
            : base(inLines)
        {

            /* Extremely ambiguous to use input parameter of same exact
             * variable name as the superclass member "lines".
             * Therefore, renaming to inLines. */

            // Inefficient to parse this into SQL when we aren't going to use
            // it as SQL.  Should probably just be removed to use the 
            // super() constructor.

            type = aType;
        }

        internal override String getResultString()
        {
            return "This section, of type '" + getType() + "' was ignored";
        }
    }

    /** Represents a Section to be Displayed, not executed */
    class DisplaySection : ParsedSection
    {

        internal DisplaySection(String[] inLines)
        {

            /* Can't user the super constructor, since it does funny things when
             * constructing the SQL Buffer, which we don't need. */
            lines = inLines;

            int firstSlash = lines[0].IndexOf('/');

            lines[0] = lines[0].Substring(firstSlash + 1);
        }

        internal override String getResultString()
        {

            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < lines.Length; i++)
            {
                if (i > 0)
                {
                    sb.Append('\n');
                }

                sb.Append("+ " + lines[i]);
            }

            return sb.ToString();
        }
    }
}
