1462 lines
		
	
	
		
			59 KiB
		
	
	
	
		
			Java
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1462 lines
		
	
	
		
			59 KiB
		
	
	
	
		
			Java
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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.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) {
 | |
|             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);
 | |
|         }
 | |
|     }
 | |
| }
 |