#!/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 names = new ArrayList(); 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 values = new ArrayList(); 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 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 values = new ArrayList(); 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 = ""; } 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 infs, ArrayList outputs, Options o, String jdbcUrl) throws IOException, SQLException { int stcount = 0; Iterator 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" + "\n +J" + "\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 inputs = new ArrayList(); ArrayList outputs = new ArrayList(); ArrayList remainArgs = new ArrayList(); 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 en = (Enumeration)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 infs = new ArrayList(); 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 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 urls = new ArrayList(); 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 driverClass = (Class)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); } } }