#!/usr/bin/env compileAndGo
# -*- coding: utf-8 mode: java -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
compiler=javac
mainClass=sqlcsv
compileAndGo

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class sqlcsv {
    static final String CLASS_NAME = sqlcsv.class.getName();

    static final Logger log = Logger.getLogger(CLASS_NAME);
    static {
        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(Level.ALL);
        log.addHandler(handler);
        log.setUseParentHandlers(false);
    }

    static final Charset UTF8;
    static {
        UTF8 = Charset.forName("UTF-8");
    }

    static final String USER_CONFDIR = System.getProperty("user.home") + "/." + CLASS_NAME;

    static final String SYSTEM_CONFDIR = "/etc/" + CLASS_NAME;

    static final String DEFAULT_CONFIG = CLASS_NAME + ".properties";

    static final String USER_CONFIG = USER_CONFDIR + "/" + DEFAULT_CONFIG,
            SYSTEM_CONFIG = SYSTEM_CONFDIR + "/" + DEFAULT_CONFIG;

    // ------------------------------------------------------------------------
    public static final File getCanonical(File file) {
        try {
            return file.getCanonicalFile();
        } catch (IOException e) {
            return file.getAbsoluteFile();
        }
    }

    private static final File[] ROOTS = File.listRoots();

    public static final boolean isRoot(File file) {
        if (file == null) return false;
        for (File root : ROOTS) {
            if (root.equals(file)) return true;
        }
        return false;
    }

    // ------------------------------------------------------------------------
    public static class ResultSetHelper {
        public static final int CLOBBUFFERSIZE = 2048;

        // note: we want to maintain compatibility with Java 5 VM's
        // These types don't exist in Java 5
        protected static final int NVARCHAR = -9, NCHAR = -15, LONGNVARCHAR = -16, NCLOB = 2011;

        public static final String[] getColumnNames(ResultSet rs) throws SQLException {
            List<String> names = new ArrayList<String>();
            ResultSetMetaData metadata = rs.getMetaData();
            for (int i = 0; i < metadata.getColumnCount(); i++) {
                names.add(metadata.getColumnName(i + 1));
            }
            String[] nameArray = new String[names.size()];
            return names.toArray(nameArray);
        }

        public String[] getColumnValues(ResultSet rs) throws SQLException, IOException {
            List<String> values = new ArrayList<String>();
            ResultSetMetaData metadata = rs.getMetaData();
            for (int i = 0; i < metadata.getColumnCount(); i++) {
                values.add(getColumnValue(rs, metadata.getColumnType(i + 1), i + 1));
            }
            String[] valueArray = new String[values.size()];
            return values.toArray(valueArray);
        }

        private String handleObject(Object obj) {
            return obj == null? "": String.valueOf(obj);
        }

        private String handleBigDecimal(BigDecimal decimal) {
            return decimal == null? "": decimal.toString();
        }

        private String handleLong(ResultSet rs, int columnIndex) throws SQLException {
            long lv = rs.getLong(columnIndex);
            return rs.wasNull()? "": Long.toString(lv);
        }

        private String handleInteger(ResultSet rs, int columnIndex) throws SQLException {
            int i = rs.getInt(columnIndex);
            return rs.wasNull()? "": Integer.toString(i);
        }

        private String handleDate(ResultSet rs, int columnIndex) throws SQLException {
            java.sql.Date date = rs.getDate(columnIndex);
            String value = null;
            if (date != null) {
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                value = dateFormat.format(date);
            }
            return value;
        }

        private String handleTime(Time time) {
            return time == null? null: time.toString();
        }

        private String handleTimestamp(Timestamp timestamp) {
            SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return timestamp == null? null: timeFormat.format(timestamp);
        }

        private String getColumnValue(ResultSet rs, int colType, int colIndex) throws SQLException,
                IOException {
            String value = "";
            switch (colType) {
            case Types.BIT:
            case Types.JAVA_OBJECT:
                value = handleObject(rs.getObject(colIndex));
                break;
            case Types.BOOLEAN:
                boolean b = rs.getBoolean(colIndex);
                value = Boolean.valueOf(b).toString();
                break;
            case NCLOB: // todo : use rs.getNClob
            case Types.CLOB:
                Clob c = rs.getClob(colIndex);
                if (c != null) value = read(c);
                break;
            case Types.BIGINT:
                value = handleLong(rs, colIndex);
                break;
            case Types.DECIMAL:
            case Types.DOUBLE:
            case Types.FLOAT:
            case Types.REAL:
            case Types.NUMERIC:
                value = handleBigDecimal(rs.getBigDecimal(colIndex));
                break;
            case Types.INTEGER:
            case Types.TINYINT:
            case Types.SMALLINT:
                value = handleInteger(rs, colIndex);
                break;
            case Types.DATE:
                value = handleDate(rs, colIndex);
                break;
            case Types.TIME:
                value = handleTime(rs.getTime(colIndex));
                break;
            case Types.TIMESTAMP:
                value = handleTimestamp(rs.getTimestamp(colIndex));
                break;
            case NVARCHAR: // todo : use rs.getNString
            case NCHAR: // todo : use rs.getNString
            case LONGNVARCHAR: // todo : use rs.getNString
            case Types.LONGVARCHAR:
            case Types.VARCHAR:
            case Types.CHAR:
                value = rs.getString(colIndex);
                break;
            default:
                value = "";
            }
            if (value == null) value = "";
            return value;

        }

        private static String read(Clob c) throws SQLException, IOException {
            StringBuilder sb = new StringBuilder((int)c.length());
            Reader r = c.getCharacterStream();
            char[] cbuf = new char[CLOBBUFFERSIZE];
            int n;
            while ((n = r.read(cbuf, 0, cbuf.length)) != -1) {
                sb.append(cbuf, 0, n);
            }
            return sb.toString();
        }
    }

    public static class CSVWriter implements Closeable {
        public static final int INITIAL_STRING_SIZE = 128;

        public static final char DEFAULT_ESCAPE_CHARACTER = '"';

        public static final char DEFAULT_SEPARATOR = ',';

        public static final char DEFAULT_QUOTE_CHARACTER = '"';

        public static final char NO_QUOTE_CHARACTER = '\u0000';

        public static final char NO_ESCAPE_CHARACTER = '\u0000';

        public static final String DEFAULT_LINE_END = "\n";

        public static final StringBuilder quoteCsv(String s, char escapechar, char quotechar,
                StringBuilder sb) {
            if (sb == null) sb = new StringBuilder(INITIAL_STRING_SIZE);
            for (int j = 0; j < s.length(); j++) {
                char nextChar = s.charAt(j);
                if (escapechar != NO_ESCAPE_CHARACTER && nextChar == quotechar) {
                    sb.append(escapechar).append(nextChar);
                } else if (escapechar != NO_ESCAPE_CHARACTER && nextChar == escapechar) {
                    sb.append(escapechar).append(nextChar);
                } else {
                    sb.append(nextChar);
                }
            }
            return sb;
        }

        public static final String quoteCsv(String s) {
            StringBuilder sb = new StringBuilder(INITIAL_STRING_SIZE);
            sb.append(DEFAULT_QUOTE_CHARACTER);
            quoteCsv(s, DEFAULT_ESCAPE_CHARACTER, DEFAULT_QUOTE_CHARACTER, sb);
            sb.append(DEFAULT_QUOTE_CHARACTER);
            return sb.toString();
        }

        public Writer out;

        protected char separator;

        protected char quotechar;

        protected char escapechar;

        protected String lineEnd;

        private ResultSetHelper resultSetHelper = new ResultSetHelper();

        public CSVWriter(Writer writer) {
            this(writer, DEFAULT_SEPARATOR);
        }

        public CSVWriter(Writer writer, char separator) {
            this(writer, separator, DEFAULT_QUOTE_CHARACTER);
        }

        public CSVWriter(Writer writer, char separator, char quotechar) {
            this(writer, separator, quotechar, DEFAULT_ESCAPE_CHARACTER);
        }

        public CSVWriter(Writer writer, char separator, char quotechar, char escapechar) {
            this(writer, separator, quotechar, escapechar, DEFAULT_LINE_END);
        }

        public CSVWriter(Writer writer, char separator, char quotechar, String lineEnd) {
            this(writer, separator, quotechar, DEFAULT_ESCAPE_CHARACTER, lineEnd);
        }

        public CSVWriter(Writer writer, char separator, char quotechar, char escapechar,
                String lineEnd) {
            this.out = writer;
            this.separator = separator;
            this.quotechar = quotechar;
            this.escapechar = escapechar;
            this.lineEnd = lineEnd;
        }

        public void writeAll(List<String[]> allLines) throws IOException {
            for (String[] line : allLines) {
                writeNext(line);
            }
        }

        protected void writeColumnNames(ResultSet rs) throws SQLException, IOException {
            writeNext(ResultSetHelper.getColumnNames(rs));
        }

        public void writeAll(java.sql.ResultSet rs, boolean includeColumnNames)
                throws SQLException, IOException {
            if (includeColumnNames) writeColumnNames(rs);
            while (rs.next()) {
                writeNext(resultSetHelper.getColumnValues(rs));
            }
        }

        public void writeNext(String[] nextLine) throws IOException {
            if (nextLine == null) return;
            StringBuilder sb = new StringBuilder(INITIAL_STRING_SIZE);
            for (int i = 0; i < nextLine.length; i++) {
                if (i != 0) sb.append(separator);
                String nextElement = nextLine[i];
                if (nextElement == null) continue;
                if (quotechar != NO_QUOTE_CHARACTER) sb.append(quotechar);
                sb.append(stringContainsSpecialCharacters(nextElement)? processLine(nextElement)
                        : nextElement);
                if (quotechar != NO_QUOTE_CHARACTER) sb.append(quotechar);
            }
            sb.append(lineEnd);
            out.write(sb.toString());
        }

        protected boolean stringContainsSpecialCharacters(String line) {
            return line.indexOf(quotechar) != -1 || line.indexOf(escapechar) != -1;
        }

        protected StringBuilder processLine(String nextElement) {
            StringBuilder sb = new StringBuilder(INITIAL_STRING_SIZE);
            for (int j = 0; j < nextElement.length(); j++) {
                char nextChar = nextElement.charAt(j);
                if (escapechar != NO_ESCAPE_CHARACTER && nextChar == quotechar) {
                    sb.append(escapechar).append(nextChar);
                } else if (escapechar != NO_ESCAPE_CHARACTER && nextChar == escapechar) {
                    sb.append(escapechar).append(nextChar);
                } else {
                    sb.append(nextChar);
                }
            }
            return sb;
        }

        public void flush() throws IOException {
            out.flush();
        }

        public void close() throws IOException {
            flush();
            out.close();
        }

        public void setResultSetHelper(ResultSetHelper resultSetHelper) {
            this.resultSetHelper = resultSetHelper;
        }
    }

    public static class SqlResultSetHelper extends ResultSetHelper {
        @Override
        public String[] getColumnValues(ResultSet rs) throws SQLException, IOException {
            List<String> values = new ArrayList<String>();
            ResultSetMetaData metadata = rs.getMetaData();
            int columnCount = metadata.getColumnCount();
            for (int i = 0; i < columnCount; i++) {
                values.add(getColumnValue(rs, metadata.getColumnType(i + 1), i + 1));
            }
            String[] valueArray = new String[values.size()];
            return values.toArray(valueArray);
        }

        private String getColumnValue(ResultSet rs, int colType, int colIndex) throws SQLException,
                IOException {
            String value = null;
            switch (colType) {
            case Types.BIT:
            case Types.JAVA_OBJECT:
                Object ov = rs.getObject(colIndex);
                if (!rs.wasNull() && ov != null) value = String.valueOf(ov);
                break;
            case Types.BOOLEAN:
                Boolean bv = rs.getBoolean(colIndex);
                if (!rs.wasNull() && bv != null) value = Boolean.toString(bv);
                break;
            case NCLOB: // todo : use rs.getNClob
            case Types.CLOB:
                Clob cv = rs.getClob(colIndex);
                if (!rs.wasNull() && cv != null) value = read(cv);
                break;
            case Types.BIGINT:
                Long lv = rs.getLong(colIndex);
                if (!rs.wasNull() && lv != null) value = Long.toString(lv);
                break;
            case Types.DECIMAL:
            case Types.DOUBLE:
            case Types.FLOAT:
            case Types.REAL:
            case Types.NUMERIC:
                BigDecimal bdv = rs.getBigDecimal(colIndex);
                if (!rs.wasNull() && bdv != null) value = bdv.toString();
                break;
            case Types.INTEGER:
            case Types.TINYINT:
            case Types.SMALLINT:
                Integer iv = rs.getInt(colIndex);
                if (!rs.wasNull() && iv != null) value = Integer.toString(iv);
                break;
            case Types.DATE:
                Date dv = rs.getDate(colIndex);
                if (!rs.wasNull() && dv != null) {
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                    value = dateFormat.format(dv);
                }
                break;
            case Types.TIME:
                Time tv = rs.getTime(colIndex);
                if (!rs.wasNull() && tv != null) value = tv.toString();
                break;
            case Types.TIMESTAMP:
                Timestamp tsv = rs.getTimestamp(colIndex);
                if (!rs.wasNull() && tsv != null) {
                    SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    value = timestampFormat.format(tsv);
                }
                break;
            case NVARCHAR: // todo : use rs.getNString
            case NCHAR: // todo : use rs.getNString
            case LONGNVARCHAR: // todo : use rs.getNString
            case Types.LONGVARCHAR:
            case Types.VARCHAR:
            case Types.CHAR:
                value = rs.getString(colIndex);
                break;
            default:
                value = "<unknown>";
            }

            return value;
        }

        private static final String read(Clob c) throws SQLException, IOException {
            StringBuilder sb = new StringBuilder((int)c.length());
            Reader r = c.getCharacterStream();
            char[] cbuf = new char[CLOBBUFFERSIZE];
            int n;
            while ((n = r.read(cbuf, 0, cbuf.length)) != -1) {
                sb.append(cbuf, 0, n);
            }
            return sb.toString();
        }
    }

    public static class SqlCSVWriter extends CSVWriter {
        public SqlCSVWriter(Writer writer) {
            this(writer, DEFAULT_SEPARATOR);
        }

        public SqlCSVWriter(Writer writer, char separator) {
            this(writer, separator, DEFAULT_QUOTE_CHARACTER);
        }

        public SqlCSVWriter(Writer writer, char separator, char quotechar) {
            this(writer, separator, quotechar, DEFAULT_ESCAPE_CHARACTER);
        }

        public SqlCSVWriter(Writer writer, char separator, char quotechar, char escapechar) {
            this(writer, separator, quotechar, escapechar, DEFAULT_LINE_END);
        }

        public SqlCSVWriter(Writer writer, char separator, char quotechar, String lineEnd) {
            this(writer, separator, quotechar, DEFAULT_ESCAPE_CHARACTER, lineEnd);
        }

        public SqlCSVWriter(Writer writer, char separator, char quotechar, char escapechar,
                String lineEnd) {
            super(writer, separator, quotechar, escapechar, lineEnd);
            setResultSetHelper(new SqlResultSetHelper());
        }

        @Override
        public void writeNext(String[] nextLine) throws IOException {
            if (nextLine == null) return;
            StringBuilder sb = new StringBuilder(INITIAL_STRING_SIZE);
            for (int i = 0; i < nextLine.length; i++) {
                if (i != 0) sb.append(separator);

                String nextElement = nextLine[i];
                if (nextElement != null) {
                    if (quotechar != NO_QUOTE_CHARACTER) sb.append(quotechar);
                    if (stringContainsSpecialCharacters(nextElement)) {
                        sb.append(processLine(nextElement));
                    } else {
                        sb.append(nextElement);
                    }
                    if (quotechar != NO_QUOTE_CHARACTER) sb.append(quotechar);
                } else {
                    sb.append("NULL");
                }
            }
            sb.append(lineEnd);
            out.write(sb.toString());
        }
    }

    // ------------------------------------------------------------------------
    static class DelegateDriver implements Driver {
        private Driver driver;

        DelegateDriver(Driver driver) {
            this.driver = driver;
        }

        public boolean acceptsURL(String u) throws SQLException {
            return driver.acceptsURL(u);
        }

        public Connection connect(String u, Properties p) throws SQLException {
            return driver.connect(u, p);
        }

        public int getMajorVersion() {
            return driver.getMajorVersion();
        }

        public int getMinorVersion() {
            return driver.getMinorVersion();
        }

        public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
            return driver.getPropertyInfo(u, p);
        }

        public boolean jdbcCompliant() {
            return driver.jdbcCompliant();
        }

        public Logger getParentLogger() {
            try {
                return (Logger)driver.getClass().getMethod("getParentLogger").invoke(driver);
            } catch (Exception e) {
                Throwable t = e;
                if (t instanceof InvocationTargetException) t = t.getCause();
                throw new RuntimeException(t);
            }
        }
    }

    static final String[] SUPPORTED_DRIVERS = new String[] {
            "org.mariadb.jdbc.Driver",
            "com.mysql.jdbc.Driver",
            "oracle.jdbc.OracleDriver",
            "org.hsqldb.jdbcDriver",
            "org.postgresql.Driver"};

    static final boolean isOracle(String jdbcUrl) {
        return jdbcUrl.startsWith("jdbc:oracle:");
    }

    static final class Exit extends RuntimeException {
        private static final long serialVersionUID = 1L;

        public Exit(int exitcode, String errorMsg) {
            this.exitcode = exitcode;
            this.errorMsg = errorMsg;
        }

        public Exit(String errorMsg) {
            this(1, errorMsg);
        }

        public Exit(int exitcode) {
            this(exitcode, null);
        }

        public Exit() {
            this(0, null);
        }

        private int exitcode;

        public int getExitcode() {
            return exitcode;
        }

        private String errorMsg;

        public String getErrorMsg() {
            return errorMsg;
        }

        public void exit(String prefix) {
            StringBuilder sb = new StringBuilder();
            if (prefix != null) sb.append(prefix);
            if (errorMsg != null) sb.append(errorMsg);
            if (sb.length() > 0) {
                System.err.println(sb.toString());
                System.err.flush();
            }
            System.exit(exitcode);
        }

        public void exit() {
            if (exitcode == 0) exit(null);
            else exit("ERROR: ");
        }
    }

    static final String getArg(String[] args, int index, String errorMsg) {
        if (args.length > index) return args[index];
        if (errorMsg == null) return null;
        throw new Exit(1, errorMsg);
    }

    static final String getExceptionAndMessage(String msg, Throwable t) {
        if (t == null) return msg;
        StringBuilder sb = new StringBuilder();
        if (msg != null) sb.append(msg);
        sb.append(t.getClass().getName());
        sb.append(": ");
        sb.append(t.getMessage());
        return sb.toString();
    }

    static final String getSummary(String msg, Throwable t) {
        if (t == null) return msg;
        StringWriter sw = new StringWriter();
        if (msg != null) sw.append(msg);
        PrintWriter pw = new PrintWriter(sw);
        t.printStackTrace(pw);
        pw.flush();
        return sw.toString();
    }

    static final void logIgnoredError(Throwable t) {
        log.info(getExceptionAndMessage("Erreur ignorée: ", t));
        log.finer(getSummary(null, t));
    }

    static final void println(String msg) {
        System.out.println(msg);
        System.out.flush();
    }

    static final void die(String msg, Throwable t) {
        if (msg != null) System.err.println(msg);
        if (t != null) t.printStackTrace(System.err);
        System.err.flush();
        System.exit(1);
    }

    static final boolean strIsempty(String str) {
        return str == null || str.length() == 0;
    }

    static final boolean strEquals(String s1, String s2, boolean ignoreCase) {
        if (ignoreCase) return s1 == s2 || (s1 != null && s1.equalsIgnoreCase(s2));
        else return s1 == s2 || (s1 != null && s1.equals(s2));
    }

    static final boolean strEquals(String[] a1, String[] a2, boolean ignoreCase) {
        if (a1 == null) return a2 == null;
        else if (a2 == null) return false;
        if (a1.length != a2.length) return false;
        for (int i = 0; i < a1.length; i++) {
            if (!strEquals(a1[i], a2[i], ignoreCase)) return false;
        }
        return true;
    }

    static final boolean strEquals(String s1, String s2) {
        return strEquals(s1, s2, false);
    }

    static final String strNotnull(String str) {
        return str == null? "": str;
    }

    static final String strSubstr(String str, int start, int end) {
        if (str == null) return null;

        int l = str.length();
        if (l > 0) {
            while (start < 0)
                start += l;
            while (end < 0)
                end += l;
        } else {
            if (start < 0) start = 0;
            if (end < 0) end = 0;
        }
        if (start >= l || start >= end) return "";
        if (end >= l + 1) end = l;

        return str.substring(start, end);
    }

    static final String strSubstr(String str, int start) {
        if (str == null) return null;
        return strSubstr(str, start, str.length());
    }

    static void close(InputStream is) {
        try {
            if (is != null) is.close();
        } catch (IOException e) {
        }
    }

    // static void close(OutputStream os) {
    // try {
    // if (os != null) os.close();
    // } catch (IOException e) {
    // }
    // }

    static void close(Reader r) {
        try {
            if (r != null) r.close();
        } catch (IOException e) {
        }
    }

    static void close(Writer w) {
        try {
            if (w != null) w.close();
        } catch (IOException e) {
        }
    }

    static void close(CSVWriter csv, String output) {
        try {
            if (csv != null && output != null) csv.close();
        } catch (IOException e) {
        }
    }

    static void close(Connection conn) {
        try {
            if (conn != null) conn.close();
        } catch (SQLException e) {
        }
    }

    static void close(PreparedStatement ps) {
        try {
            if (ps != null) ps.close();
        } catch (SQLException e) {
        }
    }

    static void close(ResultSet rs) {
        try {
            if (rs != null) rs.close();
        } catch (SQLException e) {
        }
    }

    static void rollback(Connection conn) {
        try {
            if (!conn.getAutoCommit()) conn.rollback();
        } catch (SQLException e) {
        }
    }

    static final Pattern RE_COMMENT = Pattern.compile("--|#|//");

    static String nextLine(BufferedReader inf) throws IOException {
        while (true) {
            String line = inf.readLine();
            if (line == null) return null;
            line = line.trim();
            if (!line.equals("") && !RE_COMMENT.matcher(line).lookingAt()) return line;
        }
    }

    // requêtes à ignorer, en l'occurence lignes spéciques à sqlplus pour une connexion à Oracle
    static final Pattern RE_ORACLE_IGNORE_STMT = Pattern.compile("(?i)set\\b");

    static final Pattern RE_CREATE_STMT = Pattern
            .compile("(?i)create\\s+(?:or\\s+replace)?(?:procedure|trigger|function|package)");

    static final Pattern RE_SEPARATOR = Pattern.compile(";$");

    static String nextStatement(BufferedReader inf, String jdbcUrl) throws IOException {
        boolean ignore;
        String line;
        do {
            ignore = false;
            line = nextLine(inf);
            if (line == null) return null;
            if (isOracle(jdbcUrl) && RE_ORACLE_IGNORE_STMT.matcher(line).lookingAt()) {
                ignore = true;
            }
        } while (ignore);

        StringBuilder sb = new StringBuilder();
        boolean first = true;
        if (RE_CREATE_STMT.matcher(line).lookingAt()) {
            // Ici, lire jusqu'à une ligne contenant simplement '/'
            do {
                if (line.equals("/")) break;
                if (first) first = false;
                else sb.append("\n");
                sb.append(line);
                line = nextLine(inf);
            } while (line != null);
        } else {
            // Ici, lire jusqu'à une ligne se *terminant* par ';'
            do {
                if (first) first = false;
                else sb.append(" ");
                boolean lastLine = line.endsWith(";");
                sb.append(RE_SEPARATOR.matcher(line).replaceFirst(""));
                if (lastLine) break;
                line = nextLine(inf);
            } while (line != null);
        }

        return sb.toString();
    }

    static PreparedStatement getPreparedStatement(String query, Object[] args, Connection conn)
            throws SQLException {
        PreparedStatement ps = conn.prepareStatement(query);
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                if (arg instanceof java.util.Date && !(arg instanceof Date)
                        && !(arg instanceof Time) && !(arg instanceof Timestamp)) {
                    // corriger les instances de java.util.Date en java.sql.Timestamp
                    if (arg == null || arg instanceof Timestamp) arg = (Timestamp)arg;
                    else arg = new Timestamp(((java.util.Date)arg).getTime());
                }
                ps.setObject(i + 1, arg);
            }
        }
        return ps;
    }

    SqlCSVWriter getCSVWriter(String output, boolean append) throws FileNotFoundException {
        Writer outf;
        if (output != null) {
            outf = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(output, append),
                    UTF8));
        } else {
            outf = new OutputStreamWriter(System.out);
        }
        return new SqlCSVWriter(outf);
    }

    static class Options {
        public Options(boolean ignoreIoError, boolean ignoreAnyError, boolean noHeaders,
                boolean append, boolean sameOutput, boolean forceHeaders, boolean outputUc) {
            this.ignoreIoErrors = ignoreIoError;
            this.ignoreAnyErrors = ignoreIoError;
            this.noHeaders = noHeaders;
            this.append = append;
            this.sameOutput = sameOutput;
            this.forceHeaders = forceHeaders;
            this.outputUc = outputUc;
        }

        public Options() {
            this(false, false, false, false, false, false, false);
        }

        public boolean ignoreIoErrors;

        public boolean ignoreAnyErrors;

        public boolean noHeaders;

        public boolean append;

        public boolean sameOutput;

        public boolean forceHeaders;

        public boolean outputUc;
    }

    static final String[] UC_FIELDS = new String[] {"updateCount"};

    static final Pattern RE_NAME_EXT = Pattern.compile("(.+?)(?:(\\d+))?(?:(\\.[^.]+))?");

    void executeQueries(Connection conn, Object[] args, ArrayList<BufferedReader> infs,
            ArrayList<String> outputs, Options o, String jdbcUrl) throws IOException, SQLException {
        int stcount = 0;
        Iterator<String> outputIt = outputs.iterator();
        String output = null, prevOutput = null, actualOutput = null;
        String[] fields = null, prevFields = null;
        boolean firstWrite = true;
        for (BufferedReader inf : infs) {
            try {
                while (true) {
                    String statement = nextStatement(inf, jdbcUrl);
                    if (statement == null) break;
                    stcount++;

                    log.finer(statement);
                    try {
                        PreparedStatement ps = getPreparedStatement(statement, args, conn);
                        try {
                            boolean rt = ps.execute();
                            while (true) {
                                int uc = 0;
                                boolean writeOutput;
                                if (rt) {
                                    writeOutput = true;
                                } else {
                                    uc = ps.getUpdateCount();
                                    if (uc == -1) break;
                                    writeOutput = o.outputUc;
                                }

                                SqlCSVWriter csv = null;
                                prevOutput = output;
                                prevFields = fields;
                                boolean sameOutput = false, append = false;
                                if (writeOutput) {
                                    if (outputIt.hasNext()) output = outputIt.next();
                                    if (!strEquals(output, prevOutput)) {
                                        sameOutput = false;
                                        firstWrite = true;
                                        actualOutput = output;
                                    } else if (o.sameOutput) {
                                        sameOutput = true;
                                        append = true;
                                    } else if (output != null) {
                                        String dir, basename, sindex, ext;
                                        int index = 0;
                                        sameOutput = true;
                                        File file = new File(actualOutput);
                                        dir = file.getParent();
                                        Matcher m = RE_NAME_EXT.matcher(file.getName());
                                        if (m.matches()) {
                                            basename = m.group(1);
                                            sindex = m.group(2);
                                            ext = m.group(3);
                                            if (sindex != null) index = Integer.valueOf(sindex);
                                            index++;

                                            StringBuilder sb = new StringBuilder();
                                            if (dir != null) {
                                                sb.append(dir);
                                                sb.append("/");
                                            }
                                            sb.append(basename);
                                            sb.append(index);
                                            if (ext != null) sb.append(ext);
                                            actualOutput = sb.toString();
                                            firstWrite = true;
                                            sameOutput = false;
                                        }
                                    }
                                }

                                log.fine("stmt#" + stcount);
                                ResultSet rs = null;
                                if (rt) {
                                    rs = ps.getResultSet();
                                    fields = ResultSetHelper.getColumnNames(rs);
                                } else if (o.outputUc) {
                                    fields = UC_FIELDS;
                                }
                                boolean sameFields = strEquals(fields, prevFields, true);
                                boolean writeHeaders = !o.noHeaders
                                        && !(sameFields && sameOutput && !o.forceHeaders);
                                if (writeOutput) {
                                    csv = getCSVWriter(actualOutput, append | o.append);
                                    if (sameOutput && !firstWrite
                                            && (!sameFields || o.forceHeaders)) {
                                        csv.out.write("\n");
                                    }
                                    try {
                                        if (rt) {
                                            csv.writeAll(rs, writeHeaders);
                                        } else {
                                            if (writeHeaders) csv.writeNext(UC_FIELDS);
                                            csv.writeNext(new String[] {String.valueOf(uc)});
                                        }
                                        csv.flush();
                                    } finally {
                                        close(csv, actualOutput);
                                        close(rs);
                                    }
                                }
                                firstWrite = false;
                                rt = ps.getMoreResults();
                            }
                        } finally {
                            close(ps);
                        }
                    } catch (SQLException e) {
                        if (o.ignoreAnyErrors) {
                            logIgnoredError(e);
                        } else {
                            rollback(conn);
                            throw e;
                        }
                    }
                }
            } catch (IOException e) {
                if (o.ignoreIoErrors) {
                    logIgnoredError(e);
                } else {
                    rollback(conn);
                    throw e;
                }
            }
        }
        if (!conn.getAutoCommit()) conn.commit();
    }

    static final Pattern RE_URL_PROP = Pattern.compile("(.+)\\.url$");

    void run(String[] args) throws Exception {
        if (args.length == 1 && strEquals(args[0], "--help")) {
            println("USAGE:" //
                    + "\n    "
                    + CLASS_NAME
                    + " [query]"
                    + "\n\nquery   est la requête SQL à exécuter. Si query n'est pas spécifiée ou si elle"
                    + "\n        vaut '-', la requête SQL est lue sur l'entrée standard, ou depuis un"
                    + "\n        fichier si l'option -f est spécifiée."
                    + "\n\nDEMARRAGE"
                    + "\n\nAu démarrage, les répertoires de configuration (utilisateur ~/.sqlcsv et système"
                    + "\n/etc/sqlcsv) sont analysés. Les fichiers *.jar situés dans ces répertoires sont"
                    + "\najoutés au CLASSPATH. La présence de certains fichiers est testée pour activer"
                    + "\néventuellement les logs détaillés."
                    + "\n\nOPTIONS"
                    + "\n    -J<JARDIR>"
                    + "\n    +J<JARDIR>"
                    + "\n        Spécifier un répertoire de chargement des drivers JDBC."
                    + "\n        Avec l'option -J, le répertoire spécifié est ajouté aux répertoires de"
                    + "\n        configuration par défaut ~/.sqlcsv et /etc/sqlcsv. Avec la variante +J,"
                    + "\n        seul le répertoire spécifié est analysé."
                    + "\n        Cette option *doit* être spécifiée en premier et il ne doit pas y avoir"
                    + "\n        d'espace entre l'option et son argument."
                    + "\n"
                    + "\n    -C, --config CONFIG"
                    + "\n        Prendre les informations de connexion depuis le fichier de propriété"
                    + "\n        spécifié. Pour l'identifiant CONN, la propriété 'CONN.url' doit exister"
                    + "\n        dans ce fichier avec la valeur de l'url jdbc de connexion. De plus, les"
                    + "\n        propriétés 'CONN.user' et 'CONN.password' contiennent respectivement si"
                    + "\n        nécessaire le nom et le mot de passe de connexion. La propriété"
                    + "\n        'loglevel', si elle existe, est utilisée pour configurer le niveau"
                    + "\n        d'affichage des logs, comme avec l'option --loglevel"
                    + "\n        Si cette option n'est pas spécifiée, un fichier nommé sqlcsv.properties"
                    + "\n        est recherché dans l'ordre: dans le répertoire courant, dans le"
                    + "\n        répertoire de configuration utilisateur, puis dans le répertoire de"
                    + "\n        configuration système. Si le fichier est trouvé, il est chargé"
                    + "\n        automatiquement."
                    + "\n    -l, --conn CONN"
                    + "\n        Spécifier l'identifiant (ou l'url) de connexion. Cette information est"
                    + "\n        obligatoire. Si cette option n'est pas fournie, il faut spécifier un"
                    + "\n        fichier de configuration avec l'option -C dans lequel *une seule*"
                    + "\n        propriété 'CONN.url' est définie."
                    + "\n        Si -C n'est pas spécifié et que -l est spécifié, alors l'identifiant de"
                    + "\n        connexion est enregistré dans les préférences utilisateur. Si ni -C"
                    + "\n        ni -l ne sont spécifiés, alors le dernier identifiant est utilisé s'il"
                    + "\n        existe. L'option -z permet de démarrer comme si aucun identifiant"
                    + "\n        n'avait jamais été enregistré."
                    + "\n    -j, --nop"
                    + "\n        Ne rien faire. Utile par exemple pour enregistrer l'identifiant de"
                    + "\n        connexion par défaut avec l'option -l"
                    + "\n    -u, --user USER"
                    + "\n    -p, --password PASSWORD"
                    + "\n        Spécifier un nom de connexion et un mot de passe si l'url ne le fournit"
                    + "\n        pas. Ces valeurs ont la priorité sur les valeurs éventuellement déjà"
                    + "\n        présentes dans le fichier de propriété."
                    + "\n    -f, --input INPUT"
                    + "\n        Lire la requête depuis le fichier INPUT au lieu de la lire depuis la"
                    + "\n        ligne de commande ou l'entrée standard. Ne pas spécifier cette option ou"
                    + "\n        utiliser '-' pour lire depuis l'entrée standard. Cette option est"
                    + "\n        ignorée si la requête est fournie sur la ligne de commande."
                    + "\n    -o, --output OUTPUT"
                    + "\n        Ecrire le résultat dans le fichier OUTPUT. Utiliser '-' pour spécifier"
                    + "\n        la sortie standard (c'est la valeur par défaut). S'il y a plusieurs"
                    + "\n        requêtes et que le fichier de sortie n'est pas la sortie standard,"
                    + "\n        ajouter un numéro incrémental au nom du fichier en sortie pour chaque"
                    + "\n        requête. Sinon, il est possible de spécifier plusieurs fois cette option"
                    + "\n        pour nommer les fichiers correspondant à chaque requête."
                    + "\n    -t, --autocommit"
                    + "\n        Activer le mode autocommit. Par défaut, le mode autocommit n'est pas activé"
                    + "\n    -c, --ignore-io-error"
                    + "\n        Continuer le traitement même en cas d'erreur du système de fichiers."
                    + "\n        Cependant le traitement s'arrête et la transaction est annulée si une"
                    + "\n        autre erreur se produit."
                    + "\n    -y, --ignore-any-error"
                    + "\n        Continuer le traitement même en cas d'erreur quelconque."
                    + "\n    -n, --no-headers"
                    + "\n        Ne JAMAIS inclure les en-têtes dans la sortie, même avec l'option -h"
                    + "\n    -a, --append"
                    + "\n        Ajouter le résultat au fichier OUTPUT au lieu de l'écraser."
                    + "\n    -A, --auto-na"
                    + "\n        Activer les option -n -a si le fichier OUTPUT existe et qu'il est non"
                    + "\n        vide. Le test n'est effectué que pour le premier fichier spécifié."
                    + "\n    -s, --same-output"
                    + "\n        Utiliser le même fichier pour écrire le résultat de toutes les requêtes."
                    + "\n        Normalement, un numéro incrémental est ajouté au fichier en sortie si"
                    + "\n        plusieurs requêtes sont spécifiées. Si les en-têtes sont les mêmes,"
                    + "\n        ajouter le résultat au fichier directement à la suite. Sinon, sauter une"
                    + "\n        ligne blanche et afficher les nouveaux en-têtes."
                    + "\n    -h, --force-headers"
                    + "\n        En cas d'écriture du résultat de plusieurs requêtes dans un même"
                    + "\n        fichier, ne pas tenter de concaténer les résultats même si les en-têtes"
                    + "\n        sont les mêmes."
                    + "\n    --uc-output"
                    + "\n        Ajouter dans la sortie les résultat de toutes les requêtes, pas"
                    + "\n        seulement celles de type DQML"
                    + "\n    --loglevel LOGLEVEL"
                    + "\n        Spécifier le niveau de logs à afficher. Les valeurs valides sont à"
                    + "\n        choisir parmi ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, ERROR"
                    + "\n        La présence de certains fichiers dans les répertoires de configuration"
                    + "\n        utilisateur ou système configure les logs avant que les options de la"
                    + "\n        ligne de commande ne soient analysés: un fichier DEBUG fait démarrer"
                    + "\n        l'application avec le niveau de log ALL ce qui permet de voir les logs"
                    + "\n        concernant le chargement des jar. Un fichier SQL_DEBUG permet d'activer"
                    + "\n        la trace de DriverManager. Exemple:"
                    + "\n            mkdir -p ~/.sqlcsv && touch ~/.sqlcsv/{DEBUG,SQL_DEBUG}"
                    + "\n    -v, --verbose"
                    + "\n        Equivalent à --loglevel FINER pour afficher le maximum de messages"
                    + "\n    -z, --reset-prefs"
                    + "\n        Effacer les préférences avant de faire le calcul de l'identifiant par"
                    + "\n        défaut");
            return;
        }

        String config = null, connid = null, user = null, password = null;
        Options o = new Options();
        boolean nop = false, autocommit = false, autoNa = false, resetPrefs = false;
        String loglevel = null;
        ArrayList<String> inputs = new ArrayList<String>();
        ArrayList<String> outputs = new ArrayList<String>();
        ArrayList<String> remainArgs = new ArrayList<String>();
        int i = 0, max = args.length;
        while (i < max) {
            String arg = args[i];
            if (arg.length() >= 2 && (arg.substring(0, 2).equals("-J") || arg.substring(0, 2).equals("+J"))) {
                die("L'option -J doit être spécifiée en premier", null);
            } else if (arg.equals("-C") || arg.equals("--config")) {
                config = getArg(args, ++i, "Vous devez spécifier le fichier de configuration");
            } else if (arg.equals("-l") || arg.equals("--conn")) {
                connid = getArg(args, ++i, "Vous devez spécifier l'identifiant de connexion");
            } else if (arg.equals("-j") || arg.equals("--nop")) {
                nop = true;
            } else if (arg.equals("-u") || arg.equals("--user")) {
                user = getArg(args, ++i, "Vous devez spécifier l'utilisateur de connexion");
            } else if (arg.equals("-p") || arg.equals("--password")) {
                password = getArg(args, ++i, "Vous devez spécifier l'utilisateur de connexion");
            } else if (arg.equals("-f") || arg.equals("--input")) {
                String input = getArg(args, ++i, "Vous devez spécifier le fichier en entrée");
                if (strEquals(input, "-") || strEquals(input, "/dev/stdin")) input = null;
                inputs.add(input);
            } else if (arg.equals("-o") || arg.equals("--output")) {
                String output = getArg(args, ++i, "Vous devez spécifier le fichier en sortie");
                if (strEquals(output, "-") || strEquals(output, "/dev/stdout")) output = null;
                outputs.add(output);
            } else if (arg.equals("-t") || arg.equals("--autocommit")) {
                autocommit = true;
            } else if (arg.equals("-c") || arg.equals("--ignore-io-error")) {
                o.ignoreIoErrors = true;
            } else if (arg.equals("-y") || arg.equals("--ignore-any-error")) {
                o.ignoreAnyErrors = true;
            } else if (arg.equals("-n") || arg.equals("--no-headers")) {
                o.noHeaders = true;
            } else if (arg.equals("-a") || arg.equals("--append")) {
                o.append = true;
            } else if (arg.equals("-A") || arg.equals("--auto-na")) {
                autoNa = true;
            } else if (arg.equals("-s") || arg.equals("--same-output")) {
                o.sameOutput = true;
            } else if (arg.equals("-h") || arg.equals("--force-headers")) {
                o.forceHeaders = true;
            } else if (arg.equals("--uc-output")) {
                o.outputUc = true;
            } else if (arg.equals("--loglevel")) {
                loglevel = getArg(args, ++i, "Vous devez spécifier le niveau de logs");
            } else if (arg.equals("-v") || arg.equals("--verbose")) {
                loglevel = "FINER";
            } else if (arg.equals("-z") || arg.equals("--reset-prefs")) {
                resetPrefs = true;
            } else if (arg.equals("--")) {
                i++;
                break;
            } else {
                remainArgs.add(arg);
            }
            i++;
        }
        args = remainArgs.toArray(new String[0]);

        if (loglevel != null) {
            log.setLevel(Level.parse(loglevel.toUpperCase()));
        }

        Preferences prefs = null;
        boolean usePrefs = config == null;
        if (resetPrefs || usePrefs) {
            prefs = Preferences.userNodeForPackage(sqlcsv.class);
            if (resetPrefs) prefs.clear();
            if (usePrefs && connid == null) {
                connid = prefs.get("lastConnid", null);
                if (connid != null) {
                    log.fine("Sélection de la valeur par défaut connid=" + connid);
                }
            }
        }

        // Charger les propriétés...
        // essayer depuis le répertoire courant et les répertoires parents jusqu'à $HOME
        if (config == null) {
            String userHome = System.getProperty("user.home");
            if (userHome != null && userHome.length() == 0) userHome = null;
            File dir = getCanonical(new File("."));
            File homedir = null;
            if (userHome != null) homedir = getCanonical(new File(userHome));
            while (true) {
                File file = new File(dir, DEFAULT_CONFIG);
                if (file.exists()) {
                    config = file.getPath();
                    break;
                }
                if ((homedir != null && dir.equals(homedir)) || isRoot(dir)) break;
                dir = dir.getParentFile();
                if (dir == null) break;
            }
        }
        // puis dans le répertoire de configuration utilisateur
        if (config == null && new File(USER_CONFIG).exists()) config = USER_CONFIG;
        // puis dans le répertoire de configuration système
        if (config == null && new File(SYSTEM_CONFIG).exists()) config = SYSTEM_CONFIG;

        Properties props = null;
        if (config != null) {
            log.config("Chargement des propriétés de " + config);
            props = new Properties();
            FileInputStream inf = null;
            try {
                inf = new FileInputStream(config);
                props.load(inf);
            } catch (FileNotFoundException e) {
                throw new Exit(config + ": Fichier introuvable");
            } finally {
                close(inf);
            }
        }
        if (props != null) {
            String loglevelKey = "loglevel";
            if (props.containsKey(loglevelKey)) {
                loglevel = props.getProperty(loglevelKey);
                log.setLevel(Level.parse(loglevel.toUpperCase()));
            }
        }

        String jdbcUrl = null;
        if (connid != null && connid.startsWith("jdbc:")) {
            jdbcUrl = connid;
            connid = null;
        }
        if (jdbcUrl == null && connid == null && props != null) {
            // Essayer de deviner connid en parcourant les propriétés de props
            @SuppressWarnings("unchecked")
            Enumeration<String> en = (Enumeration<String>)props.propertyNames();
            int count = 0;
            while (en.hasMoreElements()) {
                String propertyName = en.nextElement();
                Matcher m = RE_URL_PROP.matcher(propertyName);
                if (m.matches()) {
                    connid = m.group(1);
                    count++;
                }
            }
            if (count > 1) {
                // Si plusieurs configurations sont trouvées, ne pas en sélectionner une par
                // défaut
                connid = null;
            }
        }
        if (jdbcUrl == null && connid != null) {
            if (props != null) {
                String jdbcUrlKey = connid + ".url";
                if (props.containsKey(jdbcUrlKey)) jdbcUrl = props.getProperty(jdbcUrlKey);
                String userKey = connid + ".user";
                if (props.containsKey(userKey)) user = props.getProperty(userKey);
                String passwordKey = connid + ".password";
                if (props.containsKey(passwordKey)) password = props.getProperty(passwordKey);
            } else {
                jdbcUrl = connid;
            }
        }
        if (jdbcUrl == null) {
            if (resetPrefs) throw new Exit();
            else throw new Exit("Vous devez spécifier l'url de connexion");
        }
        if (usePrefs && connid != null) {
            log.fine("Enregistrement de la valeur connid=" + connid);
            prefs.put("lastConnid", connid);
        }
        if (nop) return;

        // Ouvrir les fichiers en entrée
        ArrayList<BufferedReader> infs = new ArrayList<BufferedReader>();
        boolean parseInputs = true;
        if (args.length > 0) {
            String query = args[0];
            if (!strEquals(query, "-")) {
                infs.add(new BufferedReader(new StringReader(query)));
                parseInputs = false;
            }
            // XXX analyser les arguments à transmettre à la requête
        }
        if (parseInputs) {
            if (inputs.size() == 0) inputs.add(null);
            BufferedReader inf;
            for (String input : inputs) {
                if (input == null) {
                    inf = new BufferedReader(new InputStreamReader(System.in, UTF8));
                } else {
                    inf = new BufferedReader(
                            new InputStreamReader(new FileInputStream(input), UTF8));
                }
                infs.add(inf);
            }
        }

        // Préparer la liste des fichiers en sortie
        if (outputs.size() == 0) outputs.add(null);
        String output = outputs.get(0);
        if (autoNa && output != null) {
            if (new File(output).exists()) {
                o.noHeaders = true;
                o.append = true;
            }
        }

        // Ouvrir la connexion
        log.config("Connexion à " + jdbcUrl);
        Connection conn;
        if (user == null && password == null) conn = DriverManager.getConnection(jdbcUrl);
        else conn = DriverManager.getConnection(jdbcUrl, user, password);
        conn.setAutoCommit(autocommit);

        // Exécuter les requêtes
        try {
            executeQueries(conn, null, infs, outputs, o, jdbcUrl);
        } finally {
            close(conn);
            for (BufferedReader inf : infs) {
                close(inf);
            }
        }
    }

    static final FilenameFilter JAR_FILTER = new FilenameFilter() {
        public boolean accept(File dir, String name) {
            return name.endsWith(".jar");
        }
    };

    static final boolean findJars(File confdir, ArrayList<URL> urls) {
        if (!confdir.isDirectory()) return false;
        log.config("Analyse des jars de " + confdir);
        boolean foundJar = false;
        for (File file : confdir.listFiles(JAR_FILTER)) {
            try {
                log.config("Ajout de " + file);
                urls.add(file.toURI().toURL());
                foundJar = true;
            } catch (MalformedURLException e) {
            }
        }
        return foundJar;
    }

    public static void main(String[] args) throws Exception {
        if (new File(USER_CONFDIR + "/DEBUG").exists()
                || new File(SYSTEM_CONFDIR + "/DEBUG").exists()) {
            log.setLevel(Level.ALL);
        }
        if (new File(USER_CONFDIR + "/SQL_DEBUG").exists()
                || new File(SYSTEM_CONFDIR + "/SQL_DEBUG").exists()) {
            DriverManager.setLogWriter(new PrintWriter(System.err));
        }
        boolean jardirOption = false;
        String jardir = null;
        boolean jardirOnly = false;
        if (args.length > 0) {
            String firstOption = args[0].substring(0, 2);
            if (firstOption.equals("-J")) {
                jardirOption = true;
                jardir = args[0].substring(2);
                jardirOnly = false;
            } else if (firstOption.equals("+J")) {
                jardirOption = true;
                jardir = args[0].substring(2);
                jardirOnly = true;
            }
            if (jardirOption) {
                if (jardir.length() == 0) die("L'option -J doit avoir un argument", null);
                String[] newArgs = new String[args.length - 1];
                System.arraycopy(args, 1, newArgs, 0, newArgs.length);
                args = newArgs;
            }
        }
        boolean shouldUpdateClasspath = false;
        ArrayList<URL> urls = new ArrayList<URL>();
        if (jardirOption) {
            shouldUpdateClasspath |= findJars(new File(jardir), urls);
        }
        if (!jardirOption || !jardirOnly) {
            shouldUpdateClasspath |= findJars(new File(USER_CONFDIR), urls);
            shouldUpdateClasspath |= findJars(new File(SYSTEM_CONFDIR), urls);
        }
        if (shouldUpdateClasspath) {
            ClassLoader parentLoader = Thread.currentThread().getContextClassLoader();
            ClassLoader loader = new URLClassLoader(urls.toArray(new URL[0]), parentLoader);
            Thread.currentThread().setContextClassLoader(loader);
            for (String driverName : SUPPORTED_DRIVERS) {
                try {
                    @SuppressWarnings("unchecked")
                    Class<Driver> driverClass = (Class<Driver>)loader.loadClass(driverName);
                    DriverManager.registerDriver(new DelegateDriver(driverClass.newInstance()));
                } catch (Exception e) {
                }
            }
        }

        try {
            new sqlcsv().run(args);
            System.exit(0);
        } catch (Throwable t) {
            if (t instanceof InvocationTargetException) t = t.getCause();
            if (t instanceof Exit) ((Exit)t).exit();
            die(null, t);
        }
    }
}