2016-02-17 07:59:34 +04:00
|
|
|
#!/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.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;
|
|
|
|
|
2016-10-03 11:00:41 +04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2016-02-17 07:59:34 +04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
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[] {
|
|
|
|
"com.mysql.jdbc.Driver",
|
|
|
|
"oracle.jdbc.OracleDriver",
|
|
|
|
"org.hsqldb.jdbcDriver",
|
|
|
|
"org.postgresql.Driver"};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) throws IOException {
|
|
|
|
String line = nextLine(inf);
|
|
|
|
if (line == null) return null;
|
|
|
|
|
|
|
|
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) 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);
|
|
|
|
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"
|
2016-02-18 09:31:59 +04:00
|
|
|
+ "\n/etc/sqlcsv) sont analysés. Les fichiers *.jar situés dans ces répertoires sont"
|
2016-02-17 07:59:34 +04:00
|
|
|
+ "\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 -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 -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"
|
|
|
|
+ "\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}");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
String config = null, connid = null, user = null, password = null;
|
|
|
|
Options o = new Options();
|
|
|
|
boolean autocommit = false, autoNa = 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.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("-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("--")) {
|
|
|
|
i++;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
remainArgs.add(arg);
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
args = remainArgs.toArray(new String[0]);
|
|
|
|
|
|
|
|
if (loglevel != null) {
|
|
|
|
log.setLevel(Level.parse(loglevel.toUpperCase()));
|
|
|
|
}
|
|
|
|
|
2016-10-03 11:00:41 +04:00
|
|
|
// 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;
|
|
|
|
}
|
2016-02-17 07:59:34 +04:00
|
|
|
}
|
2016-10-03 11:00:41 +04:00
|
|
|
// 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;
|
2016-02-17 07:59:34 +04:00
|
|
|
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;
|
2016-10-03 11:00:41 +04:00
|
|
|
if (connid != null && connid.startsWith("jdbc:")) {
|
|
|
|
jdbcUrl = connid;
|
|
|
|
connid = null;
|
|
|
|
}
|
|
|
|
if (jdbcUrl == null && connid == null && props != null) {
|
2016-02-17 07:59:34 +04:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
2016-10-03 11:00:41 +04:00
|
|
|
if (jdbcUrl == null && connid != null) {
|
2016-02-17 07:59:34 +04:00
|
|
|
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) throw new Exit("Vous devez spécifier l'url de connexion");
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
} 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 shouldUpdateClasspath = false;
|
|
|
|
ArrayList<URL> urls = new ArrayList<URL>();
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|