/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Ant", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 THE APACHE SOFTWARE FOUNDATION OR
 * ITS 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * This file has been changed from its original state to be able to use
 * all kinds of oracle sql code like precedures and packages ...
 * 000320 / Johan Adelöw
 *
 * Thanx to Johan. I took the liberty of rewriting some(?) code.
 * I tried to make it process as many SQL*Plus-files as possible.
 * so it can be parachuted into an existing framework.
 *
 */

package org.apache.tools.ant.taskdefs;

import org.apache.tools.ant.*;
import org.apache.tools.ant.types.*;

import java.lang.reflect.*;
import java.io.*;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Properties;
import java.util.zip.*;
import java.sql.*;

/**
 * Reads in a text file containing SQL statements seperated with semicolons
 * and executes it in a given db.
 *
 * @author <a href="mailto:jeff@custommonkey.org">Jeff Martin</a>
 * @author <A href="mailto:gholam@xtra.co.nz">Michael McCallum</A>
 * @author <A href="mailto:tim.stephenson@sybase.com">Tim Stephenson</A>
 */
public class SQLExec extends Task {

    private Method sql92parser = null;

    private int goodSql = 0, totalSql = 0;

    private Path classpath;

    private AntClassLoader loader;

    private Vector filesets = new Vector();

    private static final int _NULL  = 0;
    private static final int _TRUE  = 1;
    private static final int _FALSE = -1;

    private Transaction defaultTransaction;

    /**
     *
     */
    public SQLExec() {
        super();
        defaultTransaction = createTransaction();

        /* Create the dbms_output workaround */
//        defaultTransaction.addText ("create or replace function dbms_output2 return varchar2 is " +
//            " s varchar2(255); l pls_integer; begin dbms_output.get_line(s, l); if l = 0 then return s; " +
//            " end if; raise value_error; end; \n/");
    }

    /**
     * Database connection
     */
    private Connection conn = null;

    /**
     * Autocommit flag. Default value is false
     */
    private boolean autocommit=false;

    /**
     * SQL statement
     */
    private Statement statement = null;

    /**
     * DB driver.
     */
    private String driver = null;

    /**
     * DB url.
     */
    private String url = null;

    /**
     * DB SID
     */
    private String sid = null;

    /**
     * User name.
     */
    private String userId = null;

    /**
     * Password
     */
    private String password = null;

   /**
     * SQL input command
     */
    private String sqlCommand = "";

   /**
     * SQL input file
     */
    private File srcFile = null;

      /**
     * SQL transactions to perform
     */
    private Vector transactions = new Vector();

    /**
     * Ignore SQL*Plus commands
     */
    private boolean defaultIgnore = true;

    /**
     * Print SQL results.
     */
    private boolean print = false;

    /**
     * Print header columns.
     */
    private boolean showheaders = true;

    /**
     * Results Output file.
     */
    private File output = null;

    /**
     * RDBMS Product needed for this SQL.
     */
    private String rdbms = null;

    /**
     * RDBMS Version needed for this SQL.
     */
    private String version = null;

    /**
     * Action to perform if an error is found
     */
    private String onError = "abort";

    /**
     * Encoding to use when reading SQL statements from a file
     */
    private String encoding = null;

    /**
     * Set the classpath for loading the driver.
     */
    public void setClasspath(Path classpath) {
        if (this.classpath == null) {
            this.classpath = classpath;
        } else {
            this.classpath.append(classpath);
        }
    }

    /**
     * Create the classpath for loading the driver.
     */
    public Path createClasspath() {
        if (this.classpath == null) {
            this.classpath = new Path(project);
        }
        return this.classpath.createPath();
    }

    /**
     * Set the classpath for loading the driver using the classpath reference.
     */
    public void setClasspathRef(Reference r) {
        createClasspath().setRefid(r);
    }

    /**
     * Set the name of the sql file to be run.
     */
    public void setSrc(File srcFile) {
        defaultTransaction.setSrc(srcFile);
    }

    /**
     * Set the sql command to execute
     */
    public void addText(String sql) {
        defaultTransaction.addText(sql);
    }

 
    /**
     * Adds a set of files (nested fileset attribute).
     */
    public void addFileset(FileSet set) {
        filesets.addElement(set);
    }

    /**
     * Set the sql command to execute
     */
    public Transaction createTransaction() {
        Transaction t = new Transaction();
        transactions.addElement(t);
        return t;
    }

    /**
     * Set the JDBC driver to be used.
     */
    public void setDriver(String driver) {
        this.driver = driver;
    }

    /**
     * Set the DB connection url.
     */
    public void setUrl(String url) {
        this.url = url;
    }

    /**
     * Set the DB sid
     */
    public void setSid(String sid) {
        this.sid = sid;
    }

    /**
     * Set the user name for the DB connection.
     */
    public void setUserid(String userId) {
        this.userId = userId;
    }

    /**
     * Set the password for the DB connection.
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * Set the autocommit flag for the DB connection.
     */
    public void setAutocommit(boolean autocommit) {
        this.autocommit = autocommit;
    }

    /**
     * Set the print flag.
     */
    public void setPrint(boolean print) {
        this.print = print;
    }

    /**
     * Set the showheaders flag.
     */
    public void setShowheaders(boolean showheaders) {
        this.showheaders = showheaders;
    }

    /**
     * Set the output file.
     */
    public void setOutput(File output) {
        this.output = output;
    }

    /**
     * Set the rdbms required
     */
    public void setRdbms(String vendor) {
        this.rdbms = vendor.toLowerCase();
    }

    /**
     * Set the version required
     */
    public void setVersion(String version) {
        this.version = version.toLowerCase();
    }

    /**
     * Set the action to perform onerror
     */
    public void setOnerror(OnError action) {
        this.onError = action.getValue();
    }

    /**
     * Set the file encoding to use on the sql files read in
     *
     * @param encoding the encoding to use on the files
     */
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    /*  These are the string that are ignored by default.
     */
    private String ignoreStrings[] = 
        {"acc[ept]", "a[ppend]", "archive", "attribute", "bre[ak]", "bti[tle]", "c[hange]", 
         "cl[ear]", "col[umn]", "comp[ute]", "conn[ect]", "def[ine]", "del", "desc[ribe]", 
         "disc[onnect]", "ed[it]", "exit", "get", "help", "ho[st]", "i[nput]", "l[ist]", 
         "passw[ord]", "pau[se]", "pri[nt]", "pro[mpt]", "quit", "recover", "rem[ark]", 
         "repf[ooter]", "reph[eader]", "r[un]", "sav[e]", "sho[w]", "shutdown", "spo[ol]", 
         "startup", "store", "timi[ng]", "tti[tle]", "undef[ine]", "var[iable]", "whenever"
        };


    /** This method is used to compare SQL*Plus-commands.
     *  For example: the defining string is "exec[ute]"
     *  then "exec", "execu", "execut" and "execute" will match.
     *
     * @param s   the string that should be tested
     * @param def the defining string 
     */
    private boolean equalsExpandString(String s, String def) {
        if (s.equals(def)) {
            return true;
        }
        if (s.trim().length()==0) {
            return false;
        }
        int startpos = def.indexOf("[");
        int endpos   = def.indexOf("]");
        StringBuffer sb = new StringBuffer();
        if (startpos != -1 && endpos != -1) {
            sb.append(def.substring(0, startpos));
            for (int i = startpos + 1; i < endpos; i++) {
                if (s.equals(sb.toString())){
                    return true;
                }
                if(!s.startsWith(sb.toString())) {
                    return false;
                }
                sb.append(def.charAt(i));
            }
        }
        return s.equals(sb.toString());
    }

    
    /** Determine whether this string should be processed.
     */
    private boolean shouldIgnore(String s) {
        
        if(s.startsWith("--")||s.startsWith("//")) {
            return true;
        }
        StringTokenizer st = new StringTokenizer(s, " ;", false);
        String test = st.nextToken(); //(s.indexOf(" ") != -1)?s.substring(0, s.indexOf(" ")):s;
        if(defaultIgnore) {
            for (int i = 0 ; i < ignoreStrings.length; i++) {
                if (equalsExpandString(test, ignoreStrings[i])) {
                    return true;
                }
            }
            /* "SET" can be either SQL*Plus or a SQL-command.
            */
            if (test.equals("set") && st.hasMoreTokens()) {
                test = st.nextToken();
                if (!equalsExpandString(test, "constraint[s]") &&
                        !test.equals("role") &&
                        !test.equals("transaction")) {
                    return true;
                }
            }
        }
        return false;
    }


    /** Determine the type of block we are in.
     *  Returns _TRUE  if we are waiting for a slash
     *          _FALSE if we are not waiting for a slash
     *          _NULL  if the block-type could not be determined (yet).
     *  Note : This procedure is NOT case-sensitive because PL/SQL is not.
     */
    private int determineBlockType(String test) {
        boolean instring = false;
        StringBuffer sb = new StringBuffer(test);

        /* first we replace every occurence of "--" with "//" 
         * to make StreamTokenizer work correctly
         */
        for (int i=0; i < test.length(); i++) {
            instring = (test.charAt(i)=='\'')?!instring:instring;
            if (instring) {
                continue;
            }
            int pos = test.indexOf("--", i);
            if (pos != -1) {
                sb.replace(pos, pos + 2, "//");
            }
        }
        
        StreamTokenizer st = new StreamTokenizer(new StringReader(sb.toString()));
        st.slashSlashComments(true);
        st.slashStarComments(true);
        st.eolIsSignificant(false);
        st.lowerCaseMode(true);
        
        int word = 0;
        int ttype;
        try {
            while((ttype = st.nextToken()) != st.TT_EOF) {
                word++;
                if (ttype != st.TT_WORD) {
                    continue;
                }
                if ( word == 1 ) {
                    if (st.sval.equals("declare") ||st.sval.equals("begin")) {
                        /* Anonymous PL/SQL, should wait for slash. */
                        return _TRUE;
                    }
                    if (!st.sval.equals("create")) {
                        /* Not a create statement, no need to wait for slash */
                        return _FALSE;
                    }
                }
                
                /* Move "or replace" out of the way. */
                if ( word == 2 && st.sval.equals("or")) {
                    if ((ttype = st.nextToken()) != st.TT_WORD || !st.sval.equals("replace")) {
                        /* Encountered "or" without "replace" !? 
                         * This is not correct PL/SQL so returns _FALSE
                         */
                        return _FALSE;
                   }
                   word = 1;
                }
                
                if (word == 2) {
                    if  (st.sval.equals("java")
                            ||st.sval.equals("function")
                            ||st.sval.equals("package")
                            ||st.sval.equals("procedure")
                            ||st.sval.equals("trigger")
                            ||st.sval.equals("type")) {
                        return _TRUE;
                    }
                    return _FALSE;
                }
            }
        } catch (Exception e) {
            throw new BuildException("Error while determining block-type (" + e + ")");
        }

        return _TRUE;
    }
    
    
    /** 
     *  This method is used to process certain SQL*Plus commands and translate them into 
     *  commands that the JDBC-driver can handle.
     */
    private String translateSQLPlus(String line) {
        if (line == null || line.trim().equals("")) {
            return null;
        }
        String command = null;
        
        /* Handle "set serverout"-commands */
        StringTokenizer st = new StringTokenizer(line.toLowerCase());
        if(st.hasMoreTokens() && st.nextToken().equals("set") && st.hasMoreTokens() &&
                equalsExpandString(st.nextToken(), "serverout[put]") && st.hasMoreTokens()) {
            String onoff = st.nextToken();
            if (onoff.equals("off")) {
                command = "{call dbms_output.disable()}";
            } else { 
                if (onoff.equals("on")){
                    int buffersize = 2000;
                    if (st.hasMoreTokens() && st.nextToken().equals("size") && st.hasMoreTokens()) {
                        buffersize = Integer.parseInt(st.nextToken());
                    }
                    command =  "{call dbms_output.enable(" + buffersize + ")}";
                }
            }
        }
        return command;
    }
    

    /**
     * Load the sql file and then execute it
     */
    public void execute() throws BuildException {
        // deal with the filesets
        sqlCommand = sqlCommand.trim();
        if (srcFile == null && sqlCommand.length() == 0 && filesets.size() == 0) {
            if (transactions.size() == 0) {
                throw new BuildException("Source file, transactions, filesets or sql statement must be set!", location);
            }
        }
        else {
            for (int i=0; i<filesets.size(); i++) {
                FileSet fs = (FileSet) filesets.elementAt(i);
                DirectoryScanner ds = fs.getDirectoryScanner(project);
                File srcDir = fs.getDir(project);

                String[] srcFiles = ds.getIncludedFiles();

                // Make a transaction for each file
                for ( int j=0 ; j<srcFiles.length ; j++ ) {
                    Transaction t = createTransaction();
                    t.setSrc(new File(srcDir, srcFiles[j]));
                }
            }
        }

        if (driver == null) {
            setDriver("oracle.jdbc.driver.OracleDriver");
            log("Driver defaulted to oracle.jdbc.driver.OracleDriver.", Project.MSG_VERBOSE);
        }
        if (userId == null) {
            throw new BuildException("User Id attribute must be set!", location);
        }
        if (password == null) {
            throw new BuildException("Password attribute must be set!", location);
        }
        if (url == null && sid == null) {
            throw new BuildException("Url or Sid attribute must be set!", location);
        }

        Driver driverInstance = null;
        // Load the driver using the
        try {
            Class dc;
            if (classpath != null) {
                log("Loading " + driver + " using AntClassLoader with classpath " + classpath, Project.MSG_VERBOSE);
                loader = new AntClassLoader(project, classpath);
                dc = loader.loadClass(driver);
            } else {
                log("Loading " + driver + " using system loader.", Project.MSG_VERBOSE);
                dc = Class.forName(driver);
            }
            driverInstance = (Driver) dc.newInstance();
        } catch(ClassNotFoundException e) {
            throw new BuildException("Class Not Found: JDBC driver " + driver + " could not be loaded", location);
        } catch(IllegalAccessException e) {
            throw new BuildException("Illegal Access: JDBC driver " + driver + " could not be loaded", location);
        } catch(InstantiationException e) {
            throw new BuildException("Instantiation Exception: JDBC driver " + driver + " could not be loaded", location);
        }


        /* Trying to load the OracleSql-class for parsing of SQL92-tokens.
         */
        if (rdbms == null || rdbms.equalsIgnoreCase("oracle")) {
            try { 
                Class c;
                if (classpath != null) {
                    loader = new AntClassLoader(project, classpath);
                    c = loader.loadClass("oracle.jdbc.driver.OracleSql");
                } else {
                    c = Class.forName("oracle.jdbc.driver.OracleSql");
                }
                sql92parser = c.getMethod("parse", new Class[] { "".getClass() });
            
            } catch (Exception e) { 
                log("Could not load SQL92-parser for Oracle. Parser disabled!", Project.MSG_VERBOSE);
                sql92parser = null;
            }
        }


        try {
            Properties info = new Properties();
            info.put("user", userId);
            info.put("password", password);

            try {
                if (sid != null) {
                    log("connecting to " + sid, Project.MSG_VERBOSE );
                    conn = driverInstance.connect("jdbc:oracle:oci8:@" + sid, info);
                }
            } catch (Exception e) {
                log("Could not connect to SID, now trying URL.", Project.MSG_VERBOSE );
            }
            
            if (conn == null && url != null) {
                log("connecting to " + url, Project.MSG_VERBOSE );
                conn = driverInstance.connect(url, info);
            }

            if (conn == null) {
                // Driver doesn't understand the URL or SID
                throw new SQLException("No suitable Driver for connecting to database.");
            }

            if (!isValidRdbms(conn)) return;

            conn.setAutoCommit(autocommit);

            statement = conn.createStatement();
            statement.setEscapeProcessing(false);

            PrintStream out = System.out;
            try {
                if (output != null) {
                    log("Opening PrintStream to output file " + output, Project.MSG_VERBOSE);
                    out = new PrintStream(new BufferedOutputStream(new FileOutputStream(output)));
                }

                // Process all transactions
                for (Enumeration e = transactions.elements();
                    e.hasMoreElements();) {

                    ((Transaction) e.nextElement()).runTransaction(out);
                    if (!autocommit) {
                        log("Commiting transaction", Project.MSG_VERBOSE);
                        conn.commit();
                    }
                }
            } finally {
                if (out != null && out != System.out) {
                    out.close();
                }
            }
        } catch(Exception e) {
            if (!autocommit && conn != null && onError.equals("abort")) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {}
            }
            throw new BuildException(e, location);
        } finally {
            try {
                if (statement != null) {
                    statement.close();
                }
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {}
        }
        log(goodSql + " of " + totalSql +
            " SQL statements executed successfully");
    }
    
    
    /**
     *  Strips "/*"-comments from the beginning (and only the beginning)
     *  off a line. 
     */
    private String stripComments(String s) {

        String temp = s;
        while((temp.trim().startsWith("/*")) && temp.trim().indexOf("*/") >= 2 ) {
            int pos=temp.indexOf("*/") + 2;
            if (pos >= temp.length()) return new String("");
            temp = temp.substring(pos, temp.length()); 
        }
        return temp;
    }


    protected void runStatements(Reader reader, PrintStream out, File parent) throws SQLException, IOException {
        BufferedReader in = new BufferedReader(reader);
        StringBuffer sb = new StringBuffer();
        String command = null;
        int waitingforslash = _NULL;
        String line;
        boolean inmultilinecomment = false;        

        /* loop through all lines */
        while ((line = in.readLine()) != null) {
	    line=line = org.apache.tools.ant.ProjectHelper.replaceProperties(project, line.trim(), project.getProperties());
            if (sb.length()==0) {
                line = stripComments((inmultilinecomment)?"/*"+line:line);
                if (inmultilinecomment = line.trim().startsWith("/*")) {
                    continue;
                }
            }
              
            String check = line.trim().toLowerCase();
            String firstword = (check.indexOf(" ") != -1)?check.substring(0, check.indexOf(" ")):"";
            if (sb.length()==0) {
                if (equalsExpandString(firstword, "exec[ute]")||firstword.equals("call")) {
                    line = line.trim();
                    if (line.endsWith(";")) {
                        line = line.substring(0, line.lastIndexOf(";"));
                    }
                    command = "{call " + line.substring(line.indexOf(" ") + 1) + "}";
                } else {
                    if(check.startsWith("@") || equalsExpandString(firstword, "sta[rt]")) { 
                        /* Launch other files.
                         * First retrieve the proper filename.
                        */
                        String s = line.trim();
                        log("File found in sourcefile: " + s, Project.MSG_VERBOSE);
                        if (s.indexOf(";") != -1 ) {
                            s = s.substring(0, s.indexOf(";"));
                        }
                        /* Move "start", "@" or "@@" out of the way
                        */
                        if (firstword.startsWith("sta")) {
                            s = s.substring(s.indexOf(" ")+1).trim();
                        } else {
                            s = s.substring(s.lastIndexOf("@")+1).trim();
                        }
                        
                        /* If the line started with "@@" the filename is
                         * relative to the parent-file. Otherwise the file
                         * is relative to the workdirectory.
                        */
                        File sourcefile = null;
                        if (parent != null && check.startsWith("@@")) {
                            sourcefile = new File(parent, s);
                        } else {
                            sourcefile = new File(s);
                        }
                        Transaction t = new Transaction();
                        t.setSrc(sourcefile);
                        log("Executing file in sourcefile :", Project.MSG_VERBOSE);
                        t.runTransaction(out);
                        conn.commit();
                        continue;
                    } else {
                        if ((command = translateSQLPlus(line)) == null) {
                            if(check.equals("")||shouldIgnore(check)) {
                                continue;
                            }
                        }
                    }
                } 
            }
            
        
            if (command == null) {
                /* Ignore block-terminator */        
                if(waitingforslash == _TRUE && check.equals(".")) {
                    continue;
                }
                if(line.equals("/")){
                    command = sb.append('\n').toString();
                }
                sb.append(line).append('\n');
                if(check.indexOf(";") != -1) {
                    /* when the first ; comes in we can determine the
                     * sort of block we are dealing with 
                     */
                    if (waitingforslash == _NULL) {
                        /* determine type */
                        waitingforslash = determineBlockType(sb.toString());
                    }
                    if (waitingforslash == _FALSE  && line.trim().endsWith(";")) {
                        command = sb.toString().trim();
                        if (command.endsWith(";")) {
                            command = command.substring(0, command.length() - 1);
                        }
                        command = command + '\n';
                    }
                }
            }
            if (command != null) {
               execSQL(command, out);
               command = null;
               sb.setLength(0);
               waitingforslash = _NULL;
            }
        }
    }


    /**
     * Verify if connected to the correct RDBMS
     */
    protected boolean isValidRdbms(Connection conn) {
        if (rdbms == null && version == null)
            return true;

        try {
            DatabaseMetaData dmd = conn.getMetaData();

            if (rdbms != null) {
                String theVendor = dmd.getDatabaseProductName().toLowerCase();

                log("RDBMS = " + theVendor, Project.MSG_VERBOSE);
                if (theVendor == null || theVendor.indexOf(rdbms) < 0) {
                    log("Not the required RDBMS: "+rdbms, Project.MSG_VERBOSE);
                    return false;
                }
            }

            if (version != null) {
                String theVersion = dmd.getDatabaseProductVersion().toLowerCase();

                log("Version = " + theVersion, Project.MSG_VERBOSE);
                if (theVersion == null ||
                        !(theVersion.startsWith(version) ||
                          theVersion.indexOf(" " + version) >= 0)) {
                    log("Not the required version: \""+ version +"\"", Project.MSG_VERBOSE);
                    return false;
                }
            }
        } catch (SQLException e) {
            // Could not get the required information
            log("Failed to obtain required RDBMS information", Project.MSG_ERR);
            return false;
        }

        return true;
    }

    /**
     * Exec the sql statement.
     */
    protected void execSQL(String sql, PrintStream out) throws SQLException {
        // Check and ignore empty statements
        if ("".equals(sql.trim())) return;

        String command = sql;
        if (sql92parser != null) {
            try {
                command = (String)sql92parser.invoke(sql92parser.getDeclaringClass().newInstance(), new Object[] { sql } );
            } catch (Exception e) {
                // Parsing failed. Fallback to input-string
                command = sql;
            }
        }
        

        try {
            totalSql++;
            if (!statement.execute(command)) {
                log(statement.getUpdateCount()+" rows affected",
                    Project.MSG_VERBOSE);
            } else {
                if (print) {
                    printResults(out, showheaders);
                }
            }

            SQLWarning warning = conn.getWarnings();
            while(warning!=null) {
                log(warning + " sql warning", Project.MSG_VERBOSE);
                warning=warning.getNextWarning();
            }
            conn.clearWarnings();
            goodSql++;
        } catch (SQLException e) {
            log("Failed to execute: " + command, Project.MSG_ERR);
            if (!onError.equals("continue")) throw e;
            log(e.toString(), Project.MSG_ERR);
        }
        
        
        
        if (print) {
            try {
                while(true) {
                    statement.execute("select dbms_output2() from dual");
                        printResults(out, false);
            }
            } catch (SQLException e) {}
        }

        
        
    }

    /**
     * print any results in the statement.
     */
    protected void printResults(PrintStream out, boolean showheader) throws java.sql.SQLException {
        ResultSet rs = null;
        do {
            rs = statement.getResultSet();
            if (rs != null) {
                log("Processing new result set.", Project.MSG_VERBOSE);
                ResultSetMetaData md = rs.getMetaData();
                int columnCount = md.getColumnCount();
                StringBuffer line = new StringBuffer();
                if (showheader) {
                    for (int col = 1; col < columnCount; col++) {
                        line.append(md.getColumnName(col));
                        line.append(",");
                    }
                    line.append(md.getColumnName(columnCount));
                    out.println(line);
                    line.setLength(0);
                }
                while (rs.next()) {
                    boolean first = true;
                    for (int col = 1; col <= columnCount; col++) {
                        String columnValue = rs.getString(col);
                        if (columnValue != null) {
                            columnValue = columnValue.trim();
                        }

                        if (first) {
                            first = false;
                        } else {
                            line.append(",");
                        }
                        line.append(columnValue);
                    }
                    out.println(line);
                    line.setLength(0);
                }
            }
        } while (statement.getMoreResults());
        out.println("\n");
    }

    /**
     * Enumerated attribute with the values "continue", "stop" and "abort"
     * for the onerror attribute.
     */
    public static class OnError extends EnumeratedAttribute {
        public String[] getValues() {
            return new String[] {"continue", "stop", "abort"};
        }
    }

    /**
     * Contains the definition of a new transaction element.
     * Transactions allow several files or blocks of statements
     * to be executed using the same JDBC connection and commit
     * operation in between.
     */
    public class Transaction {
        private File tSrcFile = null;
        private String tSqlCommand = "";

        public void setSrc(File src) {
            this.tSrcFile = src;
        }

        public void addText(String sql) {
            this.tSqlCommand += sql;
        }

        private void runTransaction(PrintStream out) throws IOException, SQLException {
            if (tSqlCommand.length() != 0) {
                log("Executing commands", Project.MSG_INFO);
                runStatements(new StringReader(tSqlCommand), out, null);
            }

            if (tSrcFile != null) {
                log("Executing file: " + tSrcFile.getAbsolutePath(),
                    Project.MSG_INFO);
                Reader reader = (encoding == null) ? new FileReader(tSrcFile)
                                                   : new InputStreamReader(new FileInputStream(tSrcFile), encoding);
                runStatements(reader, out, tSrcFile.getParentFile());
                reader.close();
            }
        }
    }
}

