0001: /*
0002: * This is free software, licensed under the Gnu Public License (GPL)
0003: * get a copy from <http://www.gnu.org/licenses/gpl.html>
0004: * $Id: DumpCommand.java,v 1.37 2005/11/27 16:20:28 hzeller Exp $
0005: * author: Henner Zeller <H.Zeller@acm.org>
0006: */
0007: package henplus.commands;
0008:
0009: import henplus.AbstractCommand;
0010: import henplus.CommandDispatcher;
0011: import henplus.HenPlus;
0012: import henplus.Interruptable;
0013: import henplus.SQLMetaData;
0014: import henplus.SQLMetaDataBuilder;
0015: import henplus.SQLSession;
0016: import henplus.SigIntHandler;
0017: import henplus.Version;
0018: import henplus.sqlmodel.Table;
0019: import henplus.util.DependencyResolver;
0020: import henplus.util.DependencyResolver.ResolverResult;
0021: import henplus.view.Column;
0022: import henplus.view.ColumnMetaData;
0023: import henplus.view.TableRenderer;
0024: import henplus.view.util.NameCompleter;
0025: import henplus.view.util.CancelWriter;
0026:
0027: import henplus.view.util.ProgressWriter;
0028:
0029: import java.math.BigDecimal;
0030: import java.io.File;
0031: import java.io.FileInputStream;
0032: import java.io.FileOutputStream;
0033: import java.io.IOException;
0034: import java.io.InputStream;
0035: import java.io.InputStreamReader;
0036: import java.io.LineNumberReader;
0037: import java.io.OutputStream;
0038: import java.io.PrintStream;
0039: import java.io.Reader;
0040: import java.sql.Connection;
0041: import java.sql.DatabaseMetaData;
0042: import java.sql.ResultSetMetaData;
0043: import java.sql.PreparedStatement;
0044: import java.sql.ResultSet;
0045: import java.sql.SQLException;
0046: import java.sql.Statement;
0047: import java.sql.Time;
0048: import java.sql.Timestamp;
0049: import java.sql.Types;
0050: import java.util.ArrayList;
0051: import java.util.Collection;
0052: import java.util.HashMap;
0053: import java.util.HashSet;
0054: import java.util.Iterator;
0055: import java.util.List;
0056: import java.util.Map;
0057: import java.util.Set;
0058: import java.util.LinkedHashSet;
0059: import java.util.SortedSet;
0060: import java.util.StringTokenizer;
0061: import java.util.zip.GZIPInputStream;
0062: import java.util.zip.GZIPOutputStream;
0063:
0064: /**
0065: * Dump out and read that dump of a table; database-independently.
0066: * This reads directly from the stream, so only needs not much
0067: * memory, no matter the size of the file.
0068: ---------------------------
0069: (tabledump 'student'
0070: (dump-version 1 1)
0071: (henplus-version 0.3.3)
0072: (database-info 'MySQL - 3.23.47')
0073: (meta ('name', 'sex', 'student_id')
0074: ('STRING', 'STRING', 'INTEGER' ))
0075: (data ('Megan','F',1)
0076: ('Joseph','M',2)
0077: ('Kyle','M',3)
0078: ('Mac Donald\'s','M',44))
0079: (rows 4))
0080: ---------------------------
0081: *
0082: * QUICK AND DIRTY HACK .. NOT YET NICE. Too long. grown. Refactor..!
0083: * (create an henplus.dump package so that this can be used
0084: * @author Henner Zeller
0085: */
0086: public class DumpCommand extends AbstractCommand implements
0087: Interruptable {
0088: private final static ColumnMetaData META_HEADERS[];
0089: static {
0090: META_HEADERS = new ColumnMetaData[3];
0091: META_HEADERS[0] = new ColumnMetaData("Field");
0092: META_HEADERS[1] = new ColumnMetaData("Type");
0093: META_HEADERS[2] = new ColumnMetaData("Max. length found",
0094: ColumnMetaData.ALIGN_RIGHT);
0095: }
0096:
0097: private final String FILE_ENCODING = "UTF-8";
0098: private final static int DUMP_VERSION = 1;
0099: private final static String NULL_STR = "NULL";
0100: private final static Map JDBCTYPE2TYPENAME = new HashMap();
0101:
0102: // differentiated types by dump
0103: private final static String TYPES[] = new String[9];
0104: private final static int HP_STRING = 0;
0105: private final static int HP_INTEGER = 1;
0106: private final static int HP_NUMERIC = 2;
0107: private final static int HP_DOUBLE = 3;
0108: private final static int HP_DATE = 4;
0109: private final static int HP_TIME = 5;
0110: private final static int HP_TIMESTAMP = 6;
0111: private final static int HP_BLOB = 7;
0112: private final static int HP_CLOB = 8;
0113:
0114: static {
0115: TYPES[HP_STRING] = "STRING";
0116: TYPES[HP_INTEGER] = "INTEGER";
0117: TYPES[HP_NUMERIC] = "NUMERIC";
0118: TYPES[HP_DOUBLE] = "DOUBLE";
0119: TYPES[HP_DATE] = "DATE";
0120: TYPES[HP_TIME] = "TIME";
0121: TYPES[HP_TIMESTAMP] = "TIMESTAMP";
0122: TYPES[HP_BLOB] = "BLOB";
0123: TYPES[HP_CLOB] = "CLOB";
0124:
0125: JDBCTYPE2TYPENAME
0126: .put(new Integer(Types.CHAR), TYPES[HP_STRING]);
0127: JDBCTYPE2TYPENAME.put(new Integer(Types.VARCHAR),
0128: TYPES[HP_STRING]);
0129:
0130: // hope that, 'OTHER' can be read/written as String..
0131: JDBCTYPE2TYPENAME.put(new Integer(Types.OTHER),
0132: TYPES[HP_STRING]);
0133:
0134: JDBCTYPE2TYPENAME.put(new Integer(Types.LONGVARBINARY),
0135: TYPES[HP_BLOB]);
0136: // CLOB not supported .. try string.
0137: JDBCTYPE2TYPENAME.put(new Integer(Types.LONGVARCHAR),
0138: TYPES[HP_STRING]);
0139:
0140: // not supported yet.
0141: JDBCTYPE2TYPENAME.put(new Integer(Types.BLOB), TYPES[HP_BLOB]);
0142: // CLOB not supported .. try string.
0143: JDBCTYPE2TYPENAME
0144: .put(new Integer(Types.CLOB), TYPES[HP_STRING]);
0145:
0146: // generic float.
0147: JDBCTYPE2TYPENAME.put(new Integer(Types.DOUBLE),
0148: TYPES[HP_DOUBLE]);
0149: JDBCTYPE2TYPENAME.put(new Integer(Types.FLOAT),
0150: TYPES[HP_DOUBLE]);
0151:
0152: // generic numeric. could be integer or double
0153: JDBCTYPE2TYPENAME.put(new Integer(Types.BIGINT),
0154: TYPES[HP_NUMERIC]);
0155: JDBCTYPE2TYPENAME.put(new Integer(Types.NUMERIC),
0156: TYPES[HP_NUMERIC]);
0157: JDBCTYPE2TYPENAME.put(new Integer(Types.DECIMAL),
0158: TYPES[HP_NUMERIC]);
0159: JDBCTYPE2TYPENAME.put(new Integer(Types.BOOLEAN),
0160: TYPES[HP_NUMERIC]);
0161: // generic integer.
0162: JDBCTYPE2TYPENAME.put(new Integer(Types.INTEGER),
0163: TYPES[HP_INTEGER]);
0164: JDBCTYPE2TYPENAME.put(new Integer(Types.SMALLINT),
0165: TYPES[HP_INTEGER]);
0166: JDBCTYPE2TYPENAME.put(new Integer(Types.TINYINT),
0167: TYPES[HP_INTEGER]);
0168:
0169: JDBCTYPE2TYPENAME.put(new Integer(Types.DATE), TYPES[HP_DATE]);
0170: JDBCTYPE2TYPENAME.put(new Integer(Types.TIME), TYPES[HP_TIME]);
0171: JDBCTYPE2TYPENAME.put(new Integer(Types.TIMESTAMP),
0172: TYPES[HP_TIMESTAMP]);
0173: }
0174:
0175: private final ListUserObjectsCommand _tableCompleter;
0176: private final LoadCommand _fileOpener;
0177: private volatile boolean _running;
0178:
0179: public DumpCommand(ListUserObjectsCommand tc, LoadCommand lc) {
0180: _tableCompleter = tc;
0181: _fileOpener = lc;
0182: _running = false;
0183: }
0184:
0185: /**
0186: * returns the command-strings this command can handle.
0187: */
0188: public String[] getCommandList() {
0189: return new String[] { "dump-out", "dump-in", "verify-dump",
0190: "dump-conditional", "dump-select" };
0191: }
0192:
0193: /**
0194: * verify works without session.
0195: */
0196: public boolean requiresValidSession(String cmd) {
0197: return false;
0198: }
0199:
0200: /**
0201: * dump-in and verify-dump is complete as single-liner.
0202: * dump-out and dump-conditional needs a semicolon.
0203: */
0204: public boolean isComplete(String command) {
0205: if (command.startsWith("dump-in")
0206: || command.startsWith("verify-dump"))
0207: return true;
0208: return command.endsWith(";");
0209: }
0210:
0211: /**
0212: * execute the command given.
0213: */
0214: public int execute(SQLSession session, String cmd, String param) {
0215: //final String FILE_ENCODING = System.getProperty("file.encoding");
0216: StringTokenizer st = new StringTokenizer(param);
0217: int argc = st.countTokens();
0218:
0219: if ("dump-select".equals(cmd)) {
0220: if (session == null) {
0221: HenPlus.msg().println("not connected.");
0222: return EXEC_FAILED;
0223: }
0224: if ((argc < 4))
0225: return SYNTAX_ERROR;
0226: final String fileName = st.nextToken();
0227: final String tabName = st.nextToken();
0228: final String select = st.nextToken();
0229: if (!select.toUpperCase().equals("SELECT")) {
0230: HenPlus.msg().println("'select' expected..");
0231: return SYNTAX_ERROR;
0232: }
0233: final StringBuffer statement = new StringBuffer("select");
0234: while (st.hasMoreElements()) {
0235: statement.append(" ").append(st.nextToken());
0236: }
0237: PrintStream out = null;
0238: beginInterruptableSection();
0239: try {
0240: out = openOutputStream(fileName, FILE_ENCODING);
0241: int result = dumpSelect(session, tabName, statement
0242: .toString(), out, FILE_ENCODING);
0243: return result;
0244: } catch (Exception e) {
0245: HenPlus.msg().println("failed: " + e.getMessage());
0246: e.printStackTrace();
0247: return EXEC_FAILED;
0248: } finally {
0249: if (out != null)
0250: out.close();
0251: endInterruptableSection();
0252: }
0253: }
0254:
0255: else if ("dump-conditional".equals(cmd)) {
0256: if (session == null) {
0257: HenPlus.msg().println("not connected.");
0258: return EXEC_FAILED;
0259: }
0260: if ((argc < 2))
0261: return SYNTAX_ERROR;
0262: String fileName = (String) st.nextElement();
0263: String tabName = (String) st.nextElement();
0264: String whereClause = null;
0265: if (argc >= 3) {
0266: whereClause = st.nextToken("\n"); // till EOL
0267: whereClause = whereClause.trim();
0268: if (whereClause.toUpperCase().startsWith("WHERE")) {
0269: whereClause = whereClause.substring(5);
0270: whereClause = whereClause.trim();
0271: }
0272: }
0273: PrintStream out = null;
0274: beginInterruptableSection();
0275: try {
0276: out = openOutputStream(fileName, FILE_ENCODING);
0277: int result = dumpTable(session, tabName, whereClause,
0278: out, FILE_ENCODING);
0279: return result;
0280: } catch (Exception e) {
0281: HenPlus.msg().println("failed: " + e.getMessage());
0282: e.printStackTrace();
0283: return EXEC_FAILED;
0284: } finally {
0285: if (out != null)
0286: out.close();
0287: endInterruptableSection();
0288: }
0289: }
0290:
0291: else if ("dump-out".equals(cmd)) {
0292: if (session == null) {
0293: HenPlus.msg().println("not connected.");
0294: return EXEC_FAILED;
0295: }
0296: if ((argc < 2))
0297: return SYNTAX_ERROR;
0298: String fileName = (String) st.nextElement();
0299: PrintStream out = null;
0300: String tabName = null;
0301: beginInterruptableSection();
0302: try {
0303: final long startTime = System.currentTimeMillis();
0304: Set alreadyDumped = new HashSet(); // which tables got already dumped?
0305:
0306: out = openOutputStream(fileName, FILE_ENCODING);
0307: Set/*<String>*/tableSet = new LinkedHashSet();
0308:
0309: /* right now, we do only a sort, if there is any '*' found in tables. Probably
0310: * we might want to make this an option to dump-in */
0311: boolean needsSort = false;
0312:
0313: int dumpResult = SUCCESS;
0314:
0315: /* 1) collect tables */
0316: while (st.hasMoreElements()) {
0317: String nextToken = st.nextToken();
0318:
0319: if ("*".equals(nextToken)
0320: || nextToken.indexOf('*') > -1) {
0321: needsSort = true;
0322:
0323: Iterator iter = null;
0324:
0325: if ("*".equals(nextToken)) {
0326: iter = _tableCompleter
0327: .getTableNamesIteratorForSession(session);
0328: } else if (nextToken.indexOf('*') > -1) {
0329: String tablePrefix = nextToken.substring(0,
0330: nextToken.length() - 1);
0331: SortedSet tableNames = _tableCompleter
0332: .getTableNamesForSession(session);
0333: NameCompleter compl = new NameCompleter(
0334: tableNames);
0335: iter = compl.getAlternatives(tablePrefix);
0336: }
0337: while (iter.hasNext()) {
0338: tableSet.add(iter.next());
0339: }
0340: } else {
0341: tableSet.add(nextToken);
0342: }
0343: }
0344:
0345: /* 2) resolve dependencies */
0346: ResolverResult resolverResult = null;
0347: List/*<String>*/tableSequence;
0348: if (needsSort) {
0349: tableSequence = new ArrayList();
0350: HenPlus
0351: .msg()
0352: .println(
0353: "Retrieving and sorting tables. This may take a while, please be patient.");
0354:
0355: // get sorted tables
0356: SQLMetaData meta = new SQLMetaDataBuilder()
0357: .getMetaData(session, tableSet.iterator());
0358: DependencyResolver dr = new DependencyResolver(meta
0359: .getTables());
0360: resolverResult = dr.sortTables();
0361: List/*<Table>*/tabs = resolverResult.getTables();
0362: Iterator it = tabs.iterator();
0363: while (it.hasNext()) {
0364: tableSequence
0365: .add(((Table) it.next()).getName());
0366: }
0367: } else {
0368: tableSequence = new ArrayList(tableSet);
0369: }
0370:
0371: /* 3) dump out */
0372: if (tableSequence.size() > 1) {
0373: HenPlus.msg().println(
0374: tableSequence.size() + " tables to dump.");
0375: }
0376: Iterator it = tableSequence.iterator();
0377: while (_running && it.hasNext()) {
0378: final String table = (String) it.next();
0379: if (!alreadyDumped.contains(table)) {
0380: int result = dumpTable(session, table, null,
0381: out, FILE_ENCODING, alreadyDumped);
0382: if (result != SUCCESS) {
0383: dumpResult = result;
0384: }
0385: }
0386: }
0387:
0388: if (tableSequence.size() > 1) {
0389: final long duration = System.currentTimeMillis()
0390: - startTime;
0391: HenPlus.msg().print(
0392: "Dumping " + tableSequence.size()
0393: + " tables took ");
0394: TimeRenderer.printTime(duration, HenPlus.msg());
0395: HenPlus.msg().println();
0396: }
0397:
0398: /* 4) warn about cycles */
0399: if (resolverResult != null
0400: && resolverResult.getCyclicDependencies() != null
0401: && resolverResult.getCyclicDependencies()
0402: .size() > 0) {
0403: HenPlus
0404: .msg()
0405: .println(
0406: "-----------\n"
0407: + "NOTE: There have been cyclic dependencies between several tables detected.\n"
0408: + "These may cause trouble when dumping in the currently dumped data.");
0409: Iterator iter = resolverResult
0410: .getCyclicDependencies().iterator();
0411: int count = 0;
0412: StringBuffer sb = new StringBuffer();
0413: while (iter.hasNext()) {
0414: Iterator iter2 = ((List) iter.next())
0415: .iterator();
0416: sb.append("Cycle ").append(count).append(": ");
0417: ;
0418: while (iter2.hasNext()) {
0419: sb.append(((Table) iter2.next()).getName())
0420: .append(" -> ");
0421: }
0422: sb.delete(sb.length() - 4, sb.length()).append(
0423: '\n');
0424: }
0425: HenPlus.msg().print(sb.toString());
0426: /* todo: print out, what constraint to disable */
0427: }
0428:
0429: return dumpResult;
0430: } catch (Exception e) {
0431: HenPlus.msg().println(
0432: "dump table '" + tabName + "' failed: "
0433: + e.getMessage());
0434: e.printStackTrace();
0435: return EXEC_FAILED;
0436: } finally {
0437: if (out != null)
0438: out.close();
0439: endInterruptableSection();
0440: }
0441: }
0442:
0443: else if ("dump-in".equals(cmd)) {
0444: if (session == null) {
0445: HenPlus.msg().println(
0446: "not connected. Only verify-dump possible.");
0447: return EXEC_FAILED;
0448: }
0449: if (argc < 1 || argc > 2)
0450: return SYNTAX_ERROR;
0451: String fileName = (String) st.nextElement();
0452: int commitPoint = -1;
0453: if (argc == 2) {
0454: try {
0455: String val = (String) st.nextElement();
0456: commitPoint = Integer.valueOf(val).intValue();
0457: } catch (NumberFormatException e) {
0458: HenPlus.msg().println(
0459: "commit point number expected: " + e);
0460: return SYNTAX_ERROR;
0461: }
0462: }
0463: return retryReadDump(fileName, session, commitPoint);
0464: }
0465:
0466: else if ("verify-dump".equals(cmd)) {
0467: if (argc != 1)
0468: return SYNTAX_ERROR;
0469: String fileName = (String) st.nextElement();
0470: return retryReadDump(fileName, null, -1);
0471: }
0472: return SYNTAX_ERROR;
0473: }
0474:
0475: /**
0476: * reads a dump and does a retry if the file encoding does
0477: * not match.
0478: */
0479: private int retryReadDump(String fileName, SQLSession session,
0480: int commitPoint) {
0481: LineNumberReader in = null;
0482: boolean hot = (session != null);
0483: beginInterruptableSection();
0484: try {
0485: String fileEncoding = FILE_ENCODING;
0486: boolean retryPossible = true;
0487: do {
0488: try {
0489: in = openInputReader(fileName, fileEncoding);
0490: while (skipWhite(in)) {
0491: int result = readTableDump(in, fileEncoding,
0492: session, hot, commitPoint);
0493: retryPossible = false;
0494: if (!_running) {
0495: HenPlus.msg().println("interrupted.");
0496: return result;
0497: }
0498: if (result != SUCCESS) {
0499: return result;
0500: }
0501: }
0502: } catch (EncodingMismatchException e) {
0503: // did we already retry with another encoding?
0504: if (!fileEncoding.equals(FILE_ENCODING)) {
0505: throw new Exception(
0506: "got file encoding problem twice");
0507: }
0508: fileEncoding = e.getEncoding();
0509: HenPlus.msg().println(
0510: "got a different encoding; retry with "
0511: + fileEncoding);
0512: }
0513: } while (retryPossible);
0514: return SUCCESS;
0515: } catch (Exception e) {
0516: HenPlus.msg().println("failed: " + e.getMessage());
0517: e.printStackTrace();
0518: return EXEC_FAILED;
0519: } finally {
0520: try {
0521: if (in != null)
0522: in.close();
0523: } catch (IOException e) {
0524: HenPlus.msg().println("closing file failed.");
0525: }
0526: endInterruptableSection();
0527: }
0528: }
0529:
0530: private PrintStream openOutputStream(String fileName,
0531: String encoding) throws IOException {
0532: File f = _fileOpener.openFile(fileName);
0533: OutputStream outStream = new FileOutputStream(f);
0534: if (fileName.endsWith(".gz")) {
0535: outStream = new GZIPOutputStream(outStream, 4096);
0536: }
0537: return new PrintStream(outStream, false, encoding);
0538: }
0539:
0540: private LineNumberReader openInputReader(String fileName,
0541: String fileEncoding) throws IOException {
0542: File f = _fileOpener.openFile(fileName);
0543: InputStream inStream = new FileInputStream(f);
0544: if (fileName.endsWith(".gz")) {
0545: inStream = new GZIPInputStream(inStream);
0546: }
0547: Reader fileIn = new InputStreamReader(inStream, fileEncoding);
0548: return new LineNumberReader(fileIn);
0549: }
0550:
0551: // to make the field-name and field-type nicely aligned
0552: private void printWidth(PrintStream out, String s, int width,
0553: boolean comma) {
0554: if (comma)
0555: out.print(", ");
0556: out.print("'");
0557: out.print(s);
0558: out.print("'");
0559: for (int i = s.length(); i < width; ++i) {
0560: out.print(' ');
0561: }
0562: }
0563:
0564: private int dumpTable(SQLSession session, String tabName,
0565: String whereClause, PrintStream dumpOut,
0566: String fileEncoding, Set/*<String>*/alreadyDumped)
0567: throws Exception {
0568: int result = dumpTable(session, tabName, whereClause, dumpOut,
0569: fileEncoding);
0570: alreadyDumped.add(tabName);
0571: return result;
0572: }
0573:
0574: private int dumpSelect(SQLSession session, String exportTable,
0575: String statement, PrintStream dumpOut, String fileEncoding)
0576: throws Exception {
0577: return dumpTable(session, new SelectDumpSource(session,
0578: exportTable, statement), dumpOut, fileEncoding);
0579: }
0580:
0581: private int dumpTable(SQLSession session, String tabName,
0582: String whereClause, PrintStream dumpOut, String fileEncoding)
0583: throws Exception {
0584:
0585: // asking for meta data is only possible with the correct
0586: // table name.
0587: boolean correctName = true;
0588: if (tabName.startsWith("\"")) {
0589: //tabName = stripQuotes(tabName);
0590: correctName = false;
0591: }
0592:
0593: // separate schama and table.
0594: String schema = null;
0595: int schemaDelim = tabName.indexOf('.');
0596: if (schemaDelim > 0) {
0597: schema = tabName.substring(0, schemaDelim);
0598: tabName = tabName.substring(schemaDelim + 1);
0599: }
0600:
0601: if (correctName) {
0602: String alternative = _tableCompleter
0603: .correctTableName(tabName);
0604: if (alternative != null && !alternative.equals(tabName)) {
0605: tabName = alternative;
0606: HenPlus.out().println(
0607: "dumping table: '" + tabName
0608: + "' (corrected name)");
0609: }
0610: }
0611: final TableDumpSource tableSource = new TableDumpSource(schema,
0612: tabName, !correctName, session);
0613: tableSource.setWhereClause(whereClause);
0614: return dumpTable(session, tableSource, dumpOut, fileEncoding);
0615: }
0616:
0617: private int dumpTable(SQLSession session, DumpSource dumpSource,
0618: PrintStream dumpOut, String fileEncoding) throws Exception {
0619: final long startTime = System.currentTimeMillis();
0620: MetaProperty[] metaProps = dumpSource.getMetaProperties();
0621: if (metaProps.length == 0) {
0622: HenPlus.msg().println(
0623: "No fields in " + dumpSource.getDescription()
0624: + " found.");
0625: return EXEC_FAILED;
0626: }
0627:
0628: HenPlus.msg()
0629: .println("dump " + dumpSource.getTableName() + ":");
0630:
0631: dumpOut.println("(tabledump '" + dumpSource.getTableName()
0632: + "'");
0633: dumpOut.println(" (file-encoding '" + fileEncoding + "')");
0634: dumpOut.println(" (dump-version " + DUMP_VERSION + " "
0635: + DUMP_VERSION + ")");
0636: /*
0637: if (whereClause != null) {
0638: dumpOut.print(" (where-clause ");
0639: quoteString(dumpOut, whereClause);
0640: dumpOut.println(")");
0641: }
0642: */
0643: dumpOut.println(" (henplus-version '" + Version.getVersion()
0644: + "')");
0645: dumpOut.println(" (time '"
0646: + new Timestamp(System.currentTimeMillis()) + "')");
0647: dumpOut.print(" (database-info ");
0648: quoteString(dumpOut, session.getDatabaseInfo());
0649: dumpOut.println(")");
0650:
0651: final long expectedRows = dumpSource.getExpectedRows();
0652: dumpOut.println(" (estimated-rows '" + expectedRows + "')");
0653:
0654: dumpOut.print(" (meta (");
0655: for (int i = 0; i < metaProps.length; ++i) {
0656: final MetaProperty p = metaProps[i];
0657: printWidth(dumpOut, p.fieldName, p.renderWidth(), i != 0);
0658: }
0659: dumpOut.println(")");
0660: dumpOut.print("\t(");
0661: for (int i = 0; i < metaProps.length; ++i) {
0662: final MetaProperty p = metaProps[i];
0663: printWidth(dumpOut, p.typeName, p.renderWidth(), i != 0);
0664: }
0665: dumpOut.println("))");
0666:
0667: dumpOut.print(" (data ");
0668: ResultSet rset = null;
0669: Statement stmt = null;
0670: try {
0671: long rows = 0;
0672: ProgressWriter progressWriter = new ProgressWriter(
0673: expectedRows, HenPlus.msg());
0674: rset = dumpSource.getResultSet();
0675: stmt = dumpSource.getStatement();
0676: boolean isFirst = true;
0677: while (_running && rset.next()) {
0678: ++rows;
0679: progressWriter.update(rows);
0680: if (!isFirst)
0681: dumpOut.print("\n\t");
0682: isFirst = false;
0683: dumpOut.print("(");
0684:
0685: for (int i = 0; i < metaProps.length; ++i) {
0686: final int col = i + 1;
0687: final int this Type = metaProps[i].getType();
0688: switch (this Type) {
0689: case HP_INTEGER:
0690: case HP_NUMERIC:
0691: case HP_DOUBLE: {
0692: String val = rset.getString(col);
0693: if (rset.wasNull())
0694: dumpOut.print(NULL_STR);
0695: else
0696: dumpOut.print(val);
0697: break;
0698: }
0699:
0700: case HP_TIMESTAMP: {
0701: Timestamp val = rset.getTimestamp(col);
0702: if (rset.wasNull())
0703: dumpOut.print(NULL_STR);
0704: else {
0705: quoteString(dumpOut, val.toString());
0706: }
0707: break;
0708: }
0709:
0710: case HP_TIME: {
0711: Time val = rset.getTime(col);
0712: if (rset.wasNull())
0713: dumpOut.print(NULL_STR);
0714: else {
0715: quoteString(dumpOut, val.toString());
0716: }
0717: break;
0718: }
0719:
0720: case HP_DATE: {
0721: java.sql.Date val = rset.getDate(col);
0722: if (rset.wasNull())
0723: dumpOut.print(NULL_STR);
0724: else {
0725: quoteString(dumpOut, val.toString());
0726: }
0727: break;
0728: }
0729:
0730: case HP_STRING: {
0731: String val = rset.getString(col);
0732: if (rset.wasNull())
0733: dumpOut.print(NULL_STR);
0734: else {
0735: quoteString(dumpOut, val);
0736: }
0737: break;
0738: }
0739:
0740: default:
0741: throw new IllegalArgumentException("type "
0742: + TYPES[this Type]
0743: + " not supported yet");
0744: }
0745: if (metaProps.length > col)
0746: dumpOut.print(",");
0747: else
0748: dumpOut.print(")");
0749: }
0750: }
0751: progressWriter.finish();
0752: dumpOut.println(")");
0753: dumpOut.println(" (rows " + rows + "))\n");
0754:
0755: HenPlus.msg().print("(" + rows + " rows)\n");
0756: long execTime = System.currentTimeMillis() - startTime;
0757:
0758: HenPlus.msg()
0759: .print(
0760: "dumping '" + dumpSource.getTableName()
0761: + "' took ");
0762: TimeRenderer.printTime(execTime, HenPlus.msg());
0763: HenPlus.msg().print(" total; ");
0764: TimeRenderer.printFraction(execTime, rows, HenPlus.msg());
0765: HenPlus.msg().println(" / row");
0766: if (expectedRows >= 0 && rows != expectedRows) {
0767: HenPlus.msg().println(
0768: " == Warning: 'select count(*)' in the"
0769: + " beginning resulted in "
0770: + expectedRows
0771: + " but the dump exported " + rows
0772: + " rows == ");
0773: }
0774:
0775: if (!_running) {
0776: HenPlus
0777: .msg()
0778: .println(
0779: " == INTERRUPTED. Wait for statement to cancel.. ==");
0780: if (stmt != null)
0781: stmt.cancel();
0782: }
0783: } catch (Exception e) {
0784: //HenPlus.msg().println(selectStmt.toString());
0785: throw e; // handle later.
0786: } finally {
0787: if (rset != null) {
0788: try {
0789: rset.close();
0790: } catch (Exception e) {
0791: }
0792: }
0793: if (stmt != null) {
0794: try {
0795: stmt.close();
0796: } catch (Exception e) {
0797: }
0798: }
0799: }
0800: return SUCCESS;
0801: }
0802:
0803: private Number readNumber(LineNumberReader in) throws IOException {
0804: String token = readToken(in);
0805: // separated sign.
0806: if (token.length() == 1
0807: && (token.equals("+") || token.equals("-"))) {
0808: token += readToken(in);
0809: }
0810: if (token.equals(NULL_STR))
0811: return null;
0812: try {
0813: if (token.indexOf('.') > 0) {
0814: return Double.valueOf(token);
0815: }
0816: if (token.length() < 10) {
0817: return Integer.valueOf(token);
0818: } else if (token.length() < 19) {
0819: return Long.valueOf(token);
0820: } else {
0821: return new BigDecimal(token);
0822: }
0823: } catch (NumberFormatException e) {
0824: raiseException(in, "Number format " + token + ": "
0825: + e.getMessage());
0826: }
0827: return null;
0828: }
0829:
0830: private int readTableDump(LineNumberReader reader,
0831: String fileEncoding, SQLSession session, boolean hot,
0832: int commitPoint) throws IOException, SQLException,
0833: InterruptedException {
0834: MetaProperty[] metaProperty = null;
0835: String tableName = null;
0836: int dumpVersion = -1;
0837: int compatibleVersion = -1;
0838: String henplusVersion = null;
0839: String databaseInfo = null;
0840: String dumpTime = null;
0841: String whereClause = null;
0842: String token;
0843: long importedRows = -1;
0844: long expectedRows = -1;
0845: long estimatedRows = -1;
0846: long problemRows = -1;
0847: Connection conn = null;
0848: PreparedStatement stmt = null;
0849:
0850: expect(reader, '(');
0851: token = readToken(reader);
0852: if (!"tabledump".equals(token))
0853: raiseException(reader, "'tabledump' expected");
0854: tableName = readString(reader);
0855: long startTime = System.currentTimeMillis();
0856: while (_running) {
0857: skipWhite(reader);
0858: int rawChar = reader.read();
0859: if (rawChar == -1)
0860: return SUCCESS; // EOF reached.
0861: char inCh = (char) rawChar;
0862: if (inCh == ')')
0863: break;
0864: if (inCh != '(') {
0865: raiseException(reader, "'(' or ')' expected");
0866: }
0867: token = readToken(reader);
0868:
0869: if ("dump-version".equals(token)) {
0870: token = readToken(reader);
0871: try {
0872: dumpVersion = Integer.valueOf(token).intValue();
0873: } catch (Exception e) {
0874: raiseException(reader,
0875: "expected dump version number");
0876: }
0877: token = readToken(reader);
0878: try {
0879: compatibleVersion = Integer.valueOf(token)
0880: .intValue();
0881: } catch (Exception e) {
0882: raiseException(reader,
0883: "expected compatible version number");
0884: }
0885: checkSupported(compatibleVersion);
0886: expect(reader, ')');
0887: }
0888:
0889: else if ("file-encoding".equals(token)) {
0890: token = readString(reader);
0891: if (!token.equals(fileEncoding)) {
0892: throw new EncodingMismatchException(token);
0893: }
0894: expect(reader, ')');
0895: }
0896:
0897: else if ("henplus-version".equals(token)) {
0898: token = readString(reader);
0899: henplusVersion = token;
0900: expect(reader, ')');
0901: }
0902:
0903: else if ("rows".equals(token)) {
0904: token = readToken(reader);
0905: expectedRows = Integer.valueOf(token).intValue();
0906: expect(reader, ')');
0907: }
0908:
0909: else if ("estimated-rows".equals(token)) {
0910: token = readString(reader);
0911: estimatedRows = Integer.valueOf(token).intValue();
0912: expect(reader, ')');
0913: }
0914:
0915: else if ("database-info".equals(token)) {
0916: databaseInfo = readString(reader);
0917: expect(reader, ')');
0918: }
0919:
0920: else if ("where-clause".equals(token)) {
0921: whereClause = readString(reader);
0922: expect(reader, ')');
0923: }
0924:
0925: else if ("time".equals(token)) {
0926: dumpTime = readString(reader);
0927: expect(reader, ')');
0928: }
0929:
0930: else if ("meta".equals(token)) {
0931: if (dumpVersion < 0 || compatibleVersion < 0) {
0932: raiseException(reader,
0933: "cannot read meta data without dump-version information");
0934: }
0935: metaProperty = parseMetaData(reader);
0936: }
0937:
0938: else if ("data".equals(token)) {
0939: if (metaProperty == null) {
0940: raiseException(reader, "no meta-data available");
0941: }
0942: if (tableName == null) {
0943: raiseException(reader, "no table name known");
0944: }
0945: if (hot) {
0946: StringBuffer prep = new StringBuffer("INSERT INTO ");
0947: prep.append(tableName);
0948: prep.append(" (");
0949: for (int i = 0; i < metaProperty.length; ++i) {
0950: prep.append(metaProperty[i].fieldName);
0951: if (i + 1 < metaProperty.length)
0952: prep.append(",");
0953: }
0954: prep.append(") VALUES (");
0955: for (int i = 0; i < metaProperty.length; ++i) {
0956: prep.append("?");
0957: if (i + 1 < metaProperty.length)
0958: prep.append(",");
0959: }
0960: prep.append(")");
0961: //HenPlus.msg().println(prep.toString());
0962: conn = session.getConnection();
0963: stmt = conn.prepareStatement(prep.toString());
0964: }
0965:
0966: HenPlus.msg().println(
0967: (hot ? "importing" : "verifying")
0968: + " table dump created with HenPlus "
0969: + henplusVersion
0970: + "\nfor table : "
0971: + tableName
0972: + "\nfrom database : "
0973: + databaseInfo
0974: + "\nat : " + dumpTime
0975: + "\ndump format version : "
0976: + dumpVersion);
0977: if (whereClause != null) {
0978: HenPlus.msg().println(
0979: "projection : " + whereClause);
0980: }
0981:
0982: ProgressWriter progressWriter = new ProgressWriter(
0983: estimatedRows, HenPlus.msg());
0984: importedRows = 0;
0985: problemRows = 0;
0986: _running = true;
0987: while (_running) {
0988: skipWhite(reader);
0989: inCh = (char) reader.read();
0990: if (inCh == ')')
0991: break;
0992: if (inCh != '(') {
0993: raiseException(reader, "'(' or ')' expected");
0994: }
0995: // we are now at the beginning of the row.
0996: ++importedRows;
0997: progressWriter.update(importedRows);
0998: for (int i = 0; i < metaProperty.length; ++i) {
0999: final int col = i + 1;
1000: final int type = metaProperty[i].type;
1001: switch (type) {
1002: case HP_NUMERIC:
1003: case HP_DOUBLE:
1004: case HP_INTEGER: {
1005: Number number = readNumber(reader);
1006: if (stmt != null) {
1007: if (number == null) {
1008: if (type == HP_NUMERIC) {
1009: stmt
1010: .setNull(col,
1011: Types.NUMERIC);
1012: } else if (type == HP_INTEGER) {
1013: stmt
1014: .setNull(col,
1015: Types.INTEGER);
1016: } else if (type == HP_DOUBLE) {
1017: stmt.setNull(col, Types.DOUBLE);
1018: }
1019: } else {
1020: if (number instanceof Integer) {
1021: stmt.setInt(col, number
1022: .intValue());
1023: } else if (number instanceof Long) {
1024: stmt.setLong(col, number
1025: .longValue());
1026: } else if (number instanceof Double) {
1027: stmt.setDouble(col, number
1028: .doubleValue());
1029: } else if (number instanceof BigDecimal) {
1030: stmt.setBigDecimal(col,
1031: (BigDecimal) number);
1032: }
1033: }
1034: }
1035: break;
1036: }
1037:
1038: case HP_TIMESTAMP: {
1039: String val = readString(reader);
1040: metaProperty[i].updateMaxLength(val);
1041: if (stmt != null) {
1042: if (val == null) {
1043: stmt.setTimestamp(col, null);
1044: } else {
1045: stmt.setTimestamp(col, Timestamp
1046: .valueOf(val));
1047: }
1048: }
1049: break;
1050: }
1051:
1052: case HP_TIME: {
1053: String val = readString(reader);
1054: metaProperty[i].updateMaxLength(val);
1055: if (stmt != null) {
1056: if (val == null) {
1057: stmt.setTime(col, null);
1058: } else {
1059: stmt
1060: .setTime(col, Time
1061: .valueOf(val));
1062: }
1063: }
1064: break;
1065: }
1066:
1067: case HP_DATE: {
1068: String val = readString(reader);
1069: metaProperty[i].updateMaxLength(val);
1070: if (stmt != null) {
1071: if (val == null) {
1072: stmt.setDate(col, null);
1073: } else {
1074: stmt.setDate(col, java.sql.Date
1075: .valueOf(val));
1076: }
1077: }
1078: break;
1079: }
1080:
1081: case HP_STRING: {
1082: String val = readString(reader);
1083: metaProperty[i].updateMaxLength(val);
1084: if (stmt != null) {
1085: stmt.setString(col, val);
1086: }
1087: break;
1088: }
1089:
1090: default:
1091: throw new IllegalArgumentException("type "
1092: + TYPES[metaProperty[i].type]
1093: + " not supported yet");
1094: }
1095: expect(reader,
1096: (i + 1 < metaProperty.length) ? ','
1097: : ')');
1098: }
1099: try {
1100: if (stmt != null)
1101: stmt.execute();
1102: } catch (SQLException e) {
1103: String msg = e.getMessage();
1104: // oracle adds CR for some reason.
1105: if (msg != null)
1106: msg = msg.trim();
1107: reportProblem(msg);
1108: ++problemRows;
1109: }
1110:
1111: // commit every once in a while.
1112: if (hot && (commitPoint >= 0)
1113: && importedRows % commitPoint == 0) {
1114: conn.commit();
1115: }
1116: }
1117: progressWriter.finish();
1118: }
1119:
1120: else {
1121: HenPlus.msg()
1122: .println("ignoring unknown token " + token);
1123: dumpTime = readString(reader);
1124: expect(reader, ')');
1125: }
1126: }
1127:
1128: // return final count.
1129: finishProblemReports();
1130:
1131: if (!hot) {
1132: printMetaDataInfo(metaProperty);
1133: }
1134:
1135: // final commit, if commitPoints are enabled.
1136: if (hot && commitPoint >= 0) {
1137: conn.commit();
1138: }
1139:
1140: // we're done.
1141: if (stmt != null) {
1142: try {
1143: stmt.close();
1144: } catch (Exception e) {
1145: }
1146: }
1147:
1148: if (expectedRows >= 0 && expectedRows != importedRows) {
1149: HenPlus.msg().println(
1150: "WARNING: expected " + expectedRows + " but got "
1151: + importedRows + " rows");
1152: } else {
1153: HenPlus.msg().println("ok. ");
1154: }
1155: HenPlus.msg().print("(" + importedRows + " rows total");
1156: if (hot)
1157: HenPlus.msg().print(" / " + problemRows + " with errors");
1158: HenPlus.msg().print("; ");
1159: long execTime = System.currentTimeMillis() - startTime;
1160: TimeRenderer.printTime(execTime, HenPlus.msg());
1161: HenPlus.msg().print(" total; ");
1162: TimeRenderer.printFraction(execTime, importedRows, HenPlus
1163: .msg());
1164: HenPlus.msg().println(" / row)");
1165: return SUCCESS;
1166: }
1167:
1168: public MetaProperty[] parseMetaData(LineNumberReader in)
1169: throws IOException {
1170: List metaList = new ArrayList();
1171: expect(in, '(');
1172: for (;;) {
1173: String colName = readString(in);
1174: metaList.add(new MetaProperty(colName));
1175: skipWhite(in);
1176: char inCh = (char) in.read();
1177: if (inCh == ')')
1178: break;
1179: if (inCh != ',') {
1180: raiseException(in, "',' or ')' expected");
1181: }
1182: }
1183: expect(in, '(');
1184: MetaProperty[] result = (MetaProperty[]) metaList
1185: .toArray(new MetaProperty[metaList.size()]);
1186: for (int i = 0; i < result.length; ++i) {
1187: String typeName = readString(in);
1188: result[i].setTypeName(typeName);
1189: expect(in, (i + 1 < result.length) ? ',' : ')');
1190: }
1191: expect(in, ')');
1192: return result;
1193: }
1194:
1195: String lastProblem = null;
1196: long problemCount = 0;
1197:
1198: private void reportProblem(String msg) {
1199: if (msg == null)
1200: return;
1201: if (msg.equals(lastProblem)) {
1202: ++problemCount;
1203: } else {
1204: finishProblemReports();
1205: problemCount = 1;
1206: HenPlus.msg().print("Problem: " + msg);
1207: lastProblem = msg;
1208: }
1209: }
1210:
1211: private void finishProblemReports() {
1212: if (problemCount > 1) {
1213: HenPlus.msg().print(" (" + problemCount + " times)");
1214: }
1215: if (problemCount > 0) {
1216: HenPlus.msg().println();
1217: }
1218: lastProblem = null;
1219: problemCount = 0;
1220: }
1221:
1222: public void checkSupported(int version)
1223: throws IllegalArgumentException {
1224: if (version <= 0 || version > DUMP_VERSION) {
1225: throw new IllegalArgumentException(
1226: "incompatible dump-version");
1227: }
1228: }
1229:
1230: public void expect(LineNumberReader in, char ch) throws IOException {
1231: skipWhite(in);
1232: char inCh = (char) in.read();
1233: if (ch != inCh)
1234: raiseException(in, "'" + ch + "' expected");
1235: }
1236:
1237: private void quoteString(PrintStream out, String in) {
1238: StringBuffer buf = new StringBuffer();
1239: buf.append("'");
1240: int len = in.length();
1241: for (int i = 0; i < len; ++i) {
1242: char c = in.charAt(i);
1243: if (c == '\'' || c == '\\') {
1244: buf.append("\\");
1245: }
1246: buf.append(c);
1247: }
1248: buf.append("'");
1249: out.print(buf.toString());
1250: }
1251:
1252: /**
1253: * skip whitespace. return false, if EOF reached.
1254: */
1255: private boolean skipWhite(Reader in) throws IOException {
1256: in.mark(1);
1257: int c;
1258: while ((c = in.read()) > 0) {
1259: if (!Character.isWhitespace((char) c)) {
1260: in.reset();
1261: return true;
1262: }
1263: in.mark(1);
1264: }
1265: return false;
1266: }
1267:
1268: private String readToken(LineNumberReader in) throws IOException {
1269: skipWhite(in);
1270: StringBuffer token = new StringBuffer();
1271: in.mark(1);
1272: int c;
1273: while ((c = in.read()) > 0) {
1274: char ch = (char) c;
1275: if (Character.isWhitespace(ch) || ch == ';' || ch == ','
1276: || ch == '(' || ch == ')') {
1277: in.reset();
1278: break;
1279: }
1280: token.append(ch);
1281: in.mark(1);
1282: }
1283: return token.toString();
1284: }
1285:
1286: /**
1287: * read a string. This is either NULL without quotes or a quoted
1288: * string.
1289: */
1290: private String readString(LineNumberReader in) throws IOException {
1291: int nullParseState = 0;
1292: int c;
1293: while ((c = in.read()) > 0) {
1294: char ch = (char) c;
1295: // unless we already parse the NULL string, skip whitespaces.
1296: if (nullParseState == 0 && Character.isWhitespace(ch))
1297: continue;
1298: if (ch == '\'')
1299: break; // -> opening string.
1300: if (Character.toUpperCase(ch) == NULL_STR
1301: .charAt(nullParseState)) {
1302: ++nullParseState;
1303: if (nullParseState == NULL_STR.length())
1304: return null;
1305: continue;
1306: }
1307: raiseException(in, "unecpected character '" + ch + "'");
1308: }
1309:
1310: // ok, we found an opening quote.
1311: StringBuffer result = new StringBuffer();
1312: while ((c = in.read()) > 0) {
1313: if (c == '\\') {
1314: c = in.read();
1315: if (c < 0) {
1316: raiseException(in,
1317: "excpected character after backslash escape");
1318: }
1319: result.append((char) c);
1320: continue;
1321: }
1322: char ch = (char) c;
1323: if (ch == '\'')
1324: break; // End Of String.
1325: result.append((char) c);
1326: }
1327: return result.toString();
1328: }
1329:
1330: /**
1331: * convenience method to throw Exceptions containing the line
1332: * number
1333: */
1334: private void raiseException(LineNumberReader in, String msg)
1335: throws IOException {
1336: throw new IOException("line " + (in.getLineNumber() + 1) + ": "
1337: + msg);
1338: }
1339:
1340: private void printMetaDataInfo(MetaProperty[] prop) {
1341: HenPlus.out().println();
1342: META_HEADERS[0].resetWidth();
1343: META_HEADERS[1].resetWidth();
1344: TableRenderer table = new TableRenderer(META_HEADERS, HenPlus
1345: .out());
1346: for (int i = 0; i < prop.length; ++i) {
1347: Column[] row = new Column[3];
1348: row[0] = new Column(prop[i].getFieldName());
1349: row[1] = new Column(prop[i].getTypeName());
1350: row[2] = new Column(prop[i].getMaxLength());
1351: table.addRow(row);
1352: }
1353: table.closeTable();
1354: }
1355:
1356: //-- Interruptable interface
1357: public synchronized void interrupt() {
1358: _running = false;
1359: }
1360:
1361: private void beginInterruptableSection() {
1362: _running = true;
1363: SigIntHandler.getInstance().pushInterruptable(this );
1364: }
1365:
1366: private void endInterruptableSection() {
1367: SigIntHandler.getInstance().popInterruptable();
1368: }
1369:
1370: /**
1371: * complete the table name.
1372: */
1373: public Iterator complete(CommandDispatcher disp,
1374: String partialCommand, String lastWord) {
1375: StringTokenizer st = new StringTokenizer(partialCommand);
1376: String cmd = (String) st.nextElement();
1377: int argc = st.countTokens();
1378: if (lastWord.length() > 0) {
1379: argc--;
1380: }
1381:
1382: if ("dump-conditional".equals(cmd)) {
1383: if (argc == 0) {
1384: return new FileCompletionIterator(partialCommand,
1385: lastWord);
1386: } else if (argc == 1) {
1387: if (lastWord.startsWith("\"")) {
1388: lastWord = lastWord.substring(1);
1389: }
1390: return _tableCompleter.completeTableName(HenPlus
1391: .getInstance().getCurrentSession(), lastWord);
1392: } else if (argc > 1) {
1393: st.nextElement(); // discard filename.
1394: String table = (String) st.nextElement();
1395: Collection columns = _tableCompleter.columnsFor(table);
1396: NameCompleter compl = new NameCompleter(columns);
1397: return compl.getAlternatives(lastWord);
1398: }
1399: } else if ("dump-out".equals(cmd)) {
1400: // this is true for dump-out und verify-dump
1401: if (argc == 0) {
1402: return new FileCompletionIterator(partialCommand,
1403: lastWord);
1404: }
1405: if (argc > 0) {
1406: if (lastWord.startsWith("\"")) {
1407: lastWord = lastWord.substring(1);
1408: }
1409: final HashSet alreadyGiven = new HashSet();
1410: /*
1411: * do not complete the tables we already gave on the
1412: * commandline.
1413: */
1414: while (st.hasMoreElements()) {
1415: alreadyGiven.add(st.nextElement());
1416: }
1417:
1418: final Iterator it = _tableCompleter.completeTableName(
1419: HenPlus.getInstance().getCurrentSession(),
1420: lastWord);
1421: return new Iterator() {
1422: String table = null;
1423:
1424: public boolean hasNext() {
1425: while (it.hasNext()) {
1426: table = (String) it.next();
1427: if (alreadyGiven.contains(table)) {
1428: continue;
1429: }
1430: return true;
1431: }
1432: return false;
1433: }
1434:
1435: public Object next() {
1436: return table;
1437: }
1438:
1439: public void remove() {
1440: throw new UnsupportedOperationException("no!");
1441: }
1442: };
1443: }
1444: } else {
1445: if (argc == 0) {
1446: return new FileCompletionIterator(partialCommand,
1447: lastWord);
1448: }
1449: }
1450: return null;
1451: }
1452:
1453: /**
1454: * return a descriptive string.
1455: */
1456: public String getShortDescription() {
1457: return "handle table dumps";
1458: }
1459:
1460: public String getSynopsis(String cmd) {
1461: if ("dump-out".equals(cmd)) {
1462: return cmd + " <filename> (<tablename> | <prefix>* | *)+;";
1463: } else if ("dump-conditional".equals(cmd)) {
1464: return cmd + " <filename> <tablename> [<where-clause>]";
1465: } else if ("dump-select".equals(cmd)) {
1466: return cmd + " <filename> <exported-tablename> select ...";
1467: } else if ("dump-in".equals(cmd)) {
1468: return cmd + " <filename> [<commit-intervall>]";
1469: } else if ("verify-dump".equals(cmd)) {
1470: return cmd + " <filename>";
1471: }
1472: return cmd;
1473: }
1474:
1475: public String getLongDescription(String cmd) {
1476: String dsc = null;
1477: if ("dump-out".equals(cmd)) {
1478: dsc = "\tDump out the contents of the table(s) given to the file\n"
1479: + "\twith the given name. If the filename ends with '.gz', the\n"
1480: + "\tcontent is gzip'ed automatically .. that saves space.\n"
1481: + "\n"
1482: + "\tFor the selection of the tables you want to dump-out,\n"
1483: + "\tyou are able to use wildcards (*) to match all tables or\n"
1484: + "\ta specific set of tables.\n"
1485: + "\tE.g. you might specify \"*\" to match all tables, or\"tb_*\"\n"
1486: + "\tto match all tables starting with \"tb_\".\n"
1487: + "\n"
1488: + "\tThe dump-format allows to read in the data back into\n"
1489: + "\tthe database ('dump-in' command). And unlike pure SQL-insert\n"
1490: + "\tstatements, this works even across databases.\n"
1491: + "\tSo you can make a dump of your MySQL database and read it\n"
1492: + "\tback into Oracle, for instance. To achive this database\n"
1493: + "\tindependence, the data is stored in a canonical form\n"
1494: + "\t(e.g. the Time/Date). The file itself is human readable\n"
1495: + "\tand uses less space than simple SQL-inserts:\n"
1496: + "\t----------------\n"
1497: + "\t (tabledump 'student'\n"
1498: + "\t (dump-version 1 1)\n"
1499: + "\t (henplus-version 0.3.3)\n"
1500: + "\t (database-info 'MySQL - 3.23.47')\n"
1501: + "\t (meta ('name', 'sex', 'student_id')\n"
1502: + "\t ('STRING', 'STRING', 'INTEGER' ))\n"
1503: + "\t (data ('Megan','F',1)\n"
1504: + "\t ('Joseph','M',2)\n"
1505: + "\t ('Kyle','M',3)\n"
1506: + "\t ('Mac Donald\\'s','M',4))\n"
1507: + "\t (rows 4))\n"
1508: + "\t----------------\n\n"
1509: + "\tTODOs\n"
1510: + "\tThis format contains only the data, no\n"
1511: + "\tcanonical 'create table' statement - so the table must\n"
1512: + "\talready exist at import time. Both these features will\n"
1513: + "\tbe in later versions of HenPlus.";
1514: }
1515:
1516: else if ("dump-conditional".equals(cmd)) {
1517: dsc = "\tLike dump-out, but dump only the rows of a single table\n"
1518: + "\tthat match the where clause.";
1519: }
1520:
1521: else if ("dump-in".equals(cmd)) {
1522: dsc = "\tRead back in the data that has been dumped out with the\n"
1523: + "\t'dump-out' command. If the filename ends with '.gz',\n"
1524: + "\tthen the content is assumed to be gzipped and is\n"
1525: + "\tunpacked on the fly. The 'dump-in' command fills\n"
1526: + "\texisting tables, it does not create missing ones!\n\n"
1527: + "\tExisting content ist not deleted before, dump-in just\n"
1528: + "\tinserts all data found in the dump.\n\n"
1529: + "\tInternally, the import uses a prepared statement that is\n"
1530: + "\tfed with the typed data according to the meta data (see\n"
1531: + "\tdump-out for the file format). This evens out differences\n"
1532: + "\tbetween databases and of course enhances speed compared\n"
1533: + "\tto non-prepared statements.\n\n"
1534: + "\tThe import is done in the current transaction, unless\n"
1535: + "\tyou specify the commit-interval. The commit-interval specify\n"
1536: + "\tthe number of inserts, that are executed before an commit\n"
1537: + "\tis done. For a large amount of data this option is\n"
1538: + "\tnecessary, since otherwise your rollback-segments\n"
1539: + "\tmight get a problem ;-)";
1540: }
1541:
1542: else if ("verify-dump".equals(cmd)) {
1543: dsc = "\tLike dump-in, but a 'dry run'. Won't change anything\n"
1544: + "\tbut parses the whole file to determine whether it has\n"
1545: + "\tsyntax errors or is damaged. Any syntax errors are\n"
1546: + "\treported as it were a 'dump-in'. Problems that might\n"
1547: + "\toccur in a 'real' import in the database (that might\n"
1548: + "\tdetect, that the import would create duplicate keys for\n"
1549: + "\tinstance) can not be determined, of course.";
1550: }
1551: return dsc;
1552: }
1553:
1554: /**
1555: * A source for dumps.
1556: */
1557: private interface DumpSource {
1558: MetaProperty[] getMetaProperties() throws SQLException;
1559:
1560: String getDescription();
1561:
1562: String getTableName();
1563:
1564: Statement getStatement() throws SQLException;
1565:
1566: ResultSet getResultSet() throws SQLException;
1567:
1568: long getExpectedRows();
1569: }
1570:
1571: private static class SelectDumpSource implements DumpSource {
1572: private final SQLSession _session;
1573: private final String _sqlStat;
1574: private final String _exportTable;
1575: private MetaProperty[] _meta;
1576: private Statement _workingStatement;
1577: private ResultSet _resultSet;
1578:
1579: SelectDumpSource(SQLSession session, String exportTable,
1580: String sqlStat) {
1581: _session = session;
1582: _sqlStat = sqlStat;
1583: _exportTable = exportTable;
1584: }
1585:
1586: public MetaProperty[] getMetaProperties() throws SQLException {
1587: if (_meta != null)
1588: return _meta;
1589: ResultSet rset = getResultSet();
1590:
1591: ResultSetMetaData rsMeta = rset.getMetaData();
1592: final int cols = rsMeta.getColumnCount();
1593: _meta = new MetaProperty[cols];
1594: for (int i = 0; i < cols; ++i) {
1595: _meta[i] = new MetaProperty(
1596: rsMeta.getColumnName(i + 1), rsMeta
1597: .getColumnType(i + 1));
1598: }
1599: return _meta;
1600: }
1601:
1602: public String getDescription() {
1603: return _sqlStat;
1604: }
1605:
1606: public String getTableName() {
1607: return _exportTable;
1608: }
1609:
1610: public Statement getStatement() throws SQLException {
1611: return _workingStatement;
1612: }
1613:
1614: public ResultSet getResultSet() throws SQLException {
1615: if (_resultSet != null) {
1616: return _resultSet;
1617: }
1618: _workingStatement = _session.createStatement();
1619: try {
1620: _workingStatement.setFetchSize(1000);
1621: } catch (Exception e) {
1622: // ignore
1623: }
1624: _resultSet = _workingStatement.executeQuery(_sqlStat);
1625: return _resultSet;
1626: }
1627:
1628: public long getExpectedRows() {
1629: return -1;
1630: }
1631: }
1632:
1633: private static class TableDumpSource implements DumpSource {
1634: private final SQLSession _session;
1635: private final String _table;
1636: private final String _schema;
1637: private final boolean _caseSensitive;
1638: private MetaProperty[] _meta;
1639: private Statement _workingStatement;
1640: private String _whereClause;
1641:
1642: TableDumpSource(String schema, String table,
1643: boolean caseSensitive, SQLSession session) {
1644: _session = session;
1645: _schema = schema;
1646: _table = table;
1647: _caseSensitive = caseSensitive;
1648: }
1649:
1650: public String getDescription() {
1651: return "table '" + _table + "'";
1652: }
1653:
1654: public String getTableName() {
1655: return _table;
1656: }
1657:
1658: public void setWhereClause(String whereClause) {
1659: _whereClause = whereClause;
1660: }
1661:
1662: public Statement getStatement() {
1663: return _workingStatement;
1664: }
1665:
1666: public MetaProperty[] getMetaProperties() throws SQLException {
1667: if (_meta != null)
1668: return _meta;
1669:
1670: List metaList = new ArrayList();
1671: Connection conn = _session.getConnection();
1672: ResultSet rset = null;
1673: try {
1674: /*
1675: * if the same column is in more than one schema defined, then
1676: * oracle seems to write them out twice..
1677: */
1678: Set doubleCheck = new HashSet();
1679: DatabaseMetaData meta = conn.getMetaData();
1680: rset = meta.getColumns(conn.getCatalog(), _schema,
1681: _table, null);
1682: while (rset.next()) {
1683: String columnName = rset.getString(4);
1684: if (doubleCheck.contains(columnName))
1685: continue;
1686: doubleCheck.add(columnName);
1687: metaList.add(new MetaProperty(columnName, rset
1688: .getInt(5)));
1689: }
1690: } finally {
1691: if (rset != null) {
1692: try {
1693: rset.close();
1694: } catch (Exception e) {
1695: }
1696: }
1697: }
1698: _meta = (MetaProperty[]) metaList
1699: .toArray(new MetaProperty[metaList.size()]);
1700: return _meta;
1701: }
1702:
1703: public ResultSet getResultSet() throws SQLException {
1704: final StringBuffer selectStmt = new StringBuffer("SELECT ");
1705: for (int i = 0; i < _meta.length; ++i) {
1706: final MetaProperty p = _meta[i];
1707: if (i != 0)
1708: selectStmt.append(", ");
1709: selectStmt.append(p.fieldName);
1710: }
1711:
1712: selectStmt.append(" FROM ").append(_table);
1713: if (_whereClause != null) {
1714: selectStmt.append(" WHERE ").append(_whereClause);
1715: }
1716: _workingStatement = _session.createStatement();
1717: try {
1718: _workingStatement.setFetchSize(1000);
1719: } catch (Exception e) {
1720: // ignore
1721: }
1722: return _workingStatement
1723: .executeQuery(selectStmt.toString());
1724: }
1725:
1726: public long getExpectedRows() {
1727: CancelWriter selectInfo = new CancelWriter(HenPlus.msg());
1728: Statement stmt = null;
1729: ResultSet rset = null;
1730: try {
1731: selectInfo.print("determining number of rows...");
1732: stmt = _session.createStatement();
1733: StringBuffer countStmt = new StringBuffer(
1734: "SELECT count(*) from ");
1735: countStmt.append(_table);
1736: if (_whereClause != null) {
1737: countStmt.append(" WHERE ");
1738: countStmt.append(_whereClause);
1739: }
1740: rset = stmt.executeQuery(countStmt.toString());
1741: rset.next();
1742: return rset.getLong(1);
1743: } catch (Exception e) {
1744: return -1;
1745: } finally {
1746: if (rset != null) {
1747: try {
1748: rset.close();
1749: } catch (Exception e) {
1750: }
1751: }
1752: if (stmt != null) {
1753: try {
1754: stmt.close();
1755: } catch (Exception e) {
1756: }
1757: }
1758: selectInfo.cancel();
1759: }
1760: }
1761: }
1762:
1763: private static class MetaProperty {
1764: private int maxLen;
1765: public final String fieldName;
1766: public int type;
1767: public String typeName;
1768:
1769: public MetaProperty(String fieldName) {
1770: this .fieldName = fieldName;
1771: maxLen = -1;
1772: }
1773:
1774: public MetaProperty(String fieldName, int jdbcType) {
1775: this .fieldName = fieldName;
1776: this .typeName = (String) JDBCTYPE2TYPENAME.get(new Integer(
1777: jdbcType));
1778: if (this .typeName == null) {
1779: HenPlus.msg()
1780: .println(
1781: "cannot handle type '" + type
1782: + "' for field '"
1783: + this .fieldName
1784: + "'; trying String..");
1785: this .type = HP_STRING;
1786: this .typeName = TYPES[this .type];
1787: } else {
1788: this .type = findType(typeName);
1789: }
1790: maxLen = -1;
1791: }
1792:
1793: public String getFieldName() {
1794: return fieldName;
1795: }
1796:
1797: public String getTypeName() {
1798: return typeName;
1799: }
1800:
1801: public void setTypeName(String typeName) {
1802: this .type = findType(typeName);
1803: this .typeName = typeName;
1804: }
1805:
1806: public void updateMaxLength(String val) {
1807: if (val != null) {
1808: updateMaxLength(val.length());
1809: }
1810: }
1811:
1812: public void updateMaxLength(int maxLen) {
1813: if (maxLen > this .maxLen) {
1814: this .maxLen = maxLen;
1815: }
1816: }
1817:
1818: public int getMaxLength() {
1819: return this .maxLen;
1820: }
1821:
1822: /**
1823: * find the type in the array. uses linear search, but this is
1824: * only a small list.
1825: */
1826: private int findType(String typeName) {
1827: if (typeName == null) {
1828: throw new IllegalArgumentException("empty type ?");
1829: }
1830: typeName = typeName.toUpperCase();
1831: for (int i = 0; i < TYPES.length; ++i) {
1832: if (TYPES[i].equals(typeName))
1833: return i;
1834: }
1835: throw new IllegalArgumentException("invalid type "
1836: + typeName);
1837: }
1838:
1839: public int getType() {
1840: return type;
1841: }
1842:
1843: public int renderWidth() {
1844: return Math.max(typeName.length(), fieldName.length());
1845: }
1846: }
1847:
1848: private static class EncodingMismatchException extends IOException {
1849: private static final long serialVersionUID = 1;
1850: private final String _encoding;
1851:
1852: public EncodingMismatchException(String encoding) {
1853: super ("file encoding Mismatch Exception; got " + encoding);
1854: _encoding = encoding;
1855: }
1856:
1857: public String getEncoding() {
1858: return _encoding;
1859: }
1860: }
1861:
1862: // reading BLOBs.
1863: //private static class Base64InputStream extends InputStream { }
1864:
1865: // reading CLOBs
1866: //private static class Base64Reader extends Reader { }
1867: }
1868:
1869: /*
1870: * Local variables:
1871: * c-basic-offset: 4
1872: * compile-command: "ant -emacs -find build.xml"
1873: * End:
1874: */
|