001: /*
002: * This is free software, licensed under the Gnu Public License (GPL)
003: * get a copy from <http://www.gnu.org/licenses/gpl.html>
004: * @version $Id: TableDiffCommand.java,v 1.10 2005/11/27 16:20:28 hzeller Exp $
005: * @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
006: */
007: package henplus.plugins.tablediff;
008:
009: import henplus.AbstractCommand;
010: import henplus.CommandDispatcher;
011: import henplus.HenPlus;
012: import henplus.SQLSession;
013: import henplus.SessionManager;
014: import henplus.commands.ListUserObjectsCommand;
015: import henplus.sqlmodel.Column;
016: import henplus.sqlmodel.Table;
017: import henplus.view.util.NameCompleter;
018:
019: import java.util.ArrayList;
020: import java.util.HashSet;
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Set;
024: import java.util.SortedSet;
025: import java.util.StringTokenizer;
026:
027: public final class TableDiffCommand extends AbstractCommand {
028: protected static final String _command = "tablediff";
029: protected static final String COMMAND_DELIMITER = ";";
030: protected static final String OPTION_SINGLE_DB = "-singledb";
031:
032: /**
033: *
034: */
035: public TableDiffCommand() {
036: }
037:
038: /* (non-Javadoc)
039: * @see henplus.Command#getCommandList()
040: */
041: public String[] getCommandList() {
042: return new String[] { _command };
043: }
044:
045: /* (non-Javadoc)
046: * @see henplus.Command#participateInCommandCompletion()
047: */
048: public boolean participateInCommandCompletion() {
049: return true;
050: }
051:
052: /* (non-Javadoc)
053: * @see henplus.Command#execute(henplus.SQLSession, java.lang.String, java.lang.String)
054: */
055: public int execute(SQLSession session, String command,
056: String parameters) {
057: // first set the option for case sensitive comparison of column names
058: boolean colNameIgnoreCase = true;
059: StringTokenizer st = new StringTokenizer(parameters);
060:
061: // HenPlus.msg().println( "[execute] command: " + command + ", parameters: " + parameters );
062:
063: int result = SUCCESS;
064:
065: if (parameters.indexOf(OPTION_SINGLE_DB) != -1) {
066:
067: // required: session
068: if (session == null) {
069: HenPlus.msg().println(
070: "You need a valid session for this command.");
071: return EXEC_FAILED;
072: }
073:
074: // required: option, table1, table2
075: if (st.countTokens() != 3) {
076: return SYNTAX_ERROR;
077: }
078:
079: // push the tokenizer to skip the option
080: st.nextToken();
081: String table1 = st.nextToken();
082: String table2 = st.nextToken();
083:
084: try {
085: long start = System.currentTimeMillis();
086:
087: diffTable(session, table1, table2, colNameIgnoreCase);
088:
089: StringBuffer msg = new StringBuffer();
090: msg.append("Diffing ").append(" tables ")
091: .append(table1).append(" and ").append(table2)
092: .append(" took ").append(
093: System.currentTimeMillis() - start)
094: .append(" ms.");
095:
096: HenPlus.msg().println(msg.toString());
097:
098: } catch (Exception e) {
099: e.printStackTrace();
100: }
101:
102: } else {
103: result = executeDoubleDb(st, colNameIgnoreCase);
104: }
105:
106: return result;
107: }
108:
109: private int executeDoubleDb(StringTokenizer st,
110: boolean colNameIgnoreCase) {
111: if (st.countTokens() < 3) {
112: return SYNTAX_ERROR;
113: }
114:
115: SessionManager sessionManager = HenPlus.getInstance()
116: .getSessionManager();
117:
118: if (sessionManager.getSessionCount() < 2) {
119: System.err
120: .println("You need two valid sessions for this command.");
121: return SYNTAX_ERROR;
122: }
123:
124: SQLSession first = sessionManager.getSessionByName(st
125: .nextToken());
126: SQLSession second = sessionManager.getSessionByName(st
127: .nextToken());
128:
129: if (first == null || second == null) {
130: HenPlus.msg().println(
131: "You need two valid sessions for this command.");
132: return EXEC_FAILED;
133: } else if (first == second) {
134: HenPlus
135: .msg()
136: .println(
137: "You should specify two different sessions for this command.");
138: return EXEC_FAILED;
139: } else if (!st.hasMoreTokens()) {
140: HenPlus.msg().println(
141: "You should specify at least one table.");
142: return EXEC_FAILED;
143: }
144:
145: try {
146: long start = System.currentTimeMillis();
147: int count = 0;
148:
149: ListUserObjectsCommand objectLister = HenPlus.getInstance()
150: .getObjectLister();
151: SortedSet tablesOne = objectLister
152: .getTableNamesForSession(first);
153: SortedSet tablesTwo = objectLister
154: .getTableNamesForSession(second);
155:
156: Set alreadyDiffed = new HashSet(); // which tables got already diffed?
157:
158: /*
159: * which tables are found in the first session via wildcards but are not contained
160: * in the second session?
161: */
162: ArrayList missedFromWildcards = new ArrayList();
163:
164: while (st.hasMoreTokens()) {
165:
166: String nextToken = st.nextToken();
167:
168: if ("*".equals(nextToken)
169: || nextToken.indexOf('*') > -1) {
170: Iterator iter = null;
171:
172: if ("*".equals(nextToken))
173: iter = objectLister
174: .getTableNamesIteratorForSession(first);
175: else if (nextToken.indexOf('*') > -1) {
176: String tablePrefix = nextToken.substring(0,
177: nextToken.length() - 1);
178: NameCompleter compl = new NameCompleter(
179: tablesOne);
180: iter = compl.getAlternatives(tablePrefix);
181: }
182:
183: while (iter.hasNext()) {
184: Object objTableName = iter.next();
185: count = diffConditionally(objTableName,
186: colNameIgnoreCase, first, second,
187: tablesTwo, alreadyDiffed,
188: missedFromWildcards, count);
189: }
190: } else if (!alreadyDiffed.contains(nextToken)) {
191: diffTable(first, second, nextToken,
192: colNameIgnoreCase);
193: alreadyDiffed.add(nextToken);
194: count++;
195: }
196:
197: }
198:
199: StringBuffer msg = new StringBuffer();
200: msg.append("Diffing ").append(count).append(
201: (count == 1) ? " table took " : " tables took ")
202: .append(System.currentTimeMillis() - start).append(
203: " ms.");
204:
205: // if there were tables found via wildcards but not contained in both sessions then let
206: // the user know this.
207: if (missedFromWildcards.size() > 0) {
208: msg
209: .append("\nTables which matched a given wildcard in your first\n"
210: + "session but were not found in your second session:\n");
211: Iterator iter = missedFromWildcards.iterator();
212: while (iter.hasNext()) {
213: msg.append(iter.next()).append(", ");
214: }
215: // remove the last two chars
216: msg.delete(msg.length() - 2, msg.length());
217: }
218:
219: HenPlus.msg().println(msg.toString());
220:
221: } catch (Exception e) {
222: e.printStackTrace();
223: }
224:
225: return SUCCESS;
226: }
227:
228: private int diffConditionally(Object objTableName,
229: boolean colNameIgnoreCase, SQLSession first,
230: SQLSession second, SortedSet tablesTwo, Set alreadyDiffed,
231: List missedFromWildcards, int count) {
232: if (tablesTwo.contains(objTableName)) {
233: if (!alreadyDiffed.contains(objTableName)) {
234: String tableName = (String) objTableName;
235: diffTable(first, second, tableName, colNameIgnoreCase);
236: alreadyDiffed.add(objTableName);
237: count++;
238: }
239: } else {
240: missedFromWildcards.add(objTableName);
241: }
242: return count;
243: }
244:
245: private void diffTable(SQLSession first, SQLSession second,
246: String tableName, boolean colNameIgnoreCase) {
247: Table ref = first.getTable(tableName);
248: Table diff = second.getTable(tableName);
249: TableDiffResult diffResult = TableDiffer.diffTables(ref, diff,
250: colNameIgnoreCase);
251: if (diffResult == null) {
252: HenPlus.msg().println("No diff for table " + tableName);
253: } else {
254: HenPlus.msg().println(
255: "Diff result for table " + tableName + ":");
256: ResultTablePrinter.printResult(diffResult);
257: }
258: }
259:
260: private void diffTable(SQLSession session, String tableName1,
261: String tableName2, boolean colNameIgnoreCase) {
262: Table ref = session.getTable(tableName1);
263: Table diff = session.getTable(tableName2);
264: TableDiffResult diffResult = TableDiffer.diffTables(ref, diff,
265: colNameIgnoreCase);
266: if (diffResult == null) {
267: HenPlus.msg().println(
268: "No diff for tables " + tableName1 + " and "
269: + tableName2 + ".");
270: } else {
271: HenPlus.msg().println(
272: "Diff result for tables " + tableName1 + " and "
273: + tableName2 + ":");
274: ResultTablePrinter.printResult(diffResult);
275: }
276: }
277:
278: /* leave this for testing */
279: private TableDiffResult getMockResult() {
280: TableDiffResult result = new TableDiffResult();
281:
282: Column added = new Column("colname");
283: added.setDefault("nix");
284: added.setNullable(true);
285: added.setPosition(23);
286: added.setSize(666);
287: added.setType("myType");
288: result.addAddedColumn(added);
289:
290: Column removed = new Column("wech");
291: removed.setDefault("nix");
292: removed.setNullable(true);
293: removed.setPosition(23);
294: removed.setSize(666);
295: removed.setType("myType");
296: result.addRemovedColumn(removed);
297:
298: Column modOrg = new Column("orischinall");
299: modOrg.setDefault("orgding");
300: modOrg.setNullable(true);
301: modOrg.setPosition(23);
302: modOrg.setSize(666);
303: modOrg.setType("myType");
304:
305: Column modNew = new Column("moddifaied");
306: modNew.setDefault("modding");
307: modNew.setNullable(false);
308: modNew.setPosition(42);
309: modNew.setSize(999);
310: modNew.setType("myType");
311:
312: result.putModifiedColumns(modOrg, modNew);
313:
314: return result;
315: }
316:
317: /* (non-Javadoc)
318: * @see henplus.Command#complete(henplus.CommandDispatcher, java.lang.String, java.lang.String)
319: */
320: public Iterator complete(CommandDispatcher disp,
321: String partialCommand, final String lastWord) {
322:
323: StringTokenizer st = new StringTokenizer(partialCommand);
324: st.nextToken(); // skip cmd.
325: int argIndex = st.countTokens();
326:
327: // System.out.println("[complete] partialCommand: '"+partialCommand+"', lastWord: '" + lastWord+"'");
328: /*
329: * the following input is given:
330: * "command token1 [TAB_PRESSED]"
331: * in this case the partialCommand is "command token1", the last word has a length 0!
332: *
333: * another input:
334: * "command toke[TAB_PRESSED]"
335: * then the partialCommand is "command toke", the last word is "toke".
336: */
337: if (lastWord.length() > 0) {
338: argIndex--;
339: }
340:
341: // ========================= singledb =======================
342:
343: // check completion for --singledb
344: if (argIndex == 0 && lastWord.startsWith("-")) {
345: return new Iterator() {
346: private boolean _next = true;
347:
348: public boolean hasNext() {
349: return _next;
350: }
351:
352: public Object next() {
353: _next = false;
354: return OPTION_SINGLE_DB;
355: }
356:
357: public void remove() { /* do nothing */
358: }
359: };
360: } else if (partialCommand.indexOf(OPTION_SINGLE_DB) != -1
361: && argIndex > 0) {
362:
363: SessionManager sessionManager = HenPlus.getInstance()
364: .getSessionManager();
365: SQLSession session = sessionManager.getCurrentSession();
366:
367: final HashSet alreadyGiven = new HashSet();
368: while (st.hasMoreElements()) {
369: alreadyGiven.add(st.nextToken());
370: }
371: ListUserObjectsCommand objectList = HenPlus.getInstance()
372: .getObjectLister();
373: final Iterator iter = objectList.completeTableName(session,
374: lastWord);
375: return new Iterator() {
376: String table = null;
377:
378: public boolean hasNext() {
379: while (iter.hasNext()) {
380: table = (String) iter.next();
381: if (alreadyGiven.contains(table)
382: && !lastWord.equals(table)) {
383: continue;
384: }
385: return true;
386: }
387: return false;
388: }
389:
390: public Object next() {
391: return table;
392: }
393:
394: public void remove() {
395: throw new UnsupportedOperationException("no!");
396: }
397: };
398:
399: }
400:
401: // ========================= !singledb =======================
402:
403: // !singledb && process the first session
404: else if (partialCommand.indexOf(OPTION_SINGLE_DB) == -1
405: && argIndex == 0) {
406: return HenPlus.getInstance().getSessionManager()
407: .completeSessionName(lastWord);
408: }
409: // !singledb && process the second session
410: else if (partialCommand.indexOf(OPTION_SINGLE_DB) == -1
411: && argIndex == 1) {
412: final String firstSession = st.nextToken();
413: return getSecondSessionCompleter(lastWord, firstSession);
414: }
415: // process tables
416: else if (argIndex > 1) {
417: SessionManager sessionManager = HenPlus.getInstance()
418: .getSessionManager();
419: SQLSession first = sessionManager.getSessionByName(st
420: .nextToken());
421: SQLSession second = sessionManager.getSessionByName(st
422: .nextToken());
423:
424: final HashSet alreadyGiven = new HashSet();
425: while (st.hasMoreElements()) {
426: alreadyGiven.add(st.nextToken());
427: }
428: ListUserObjectsCommand objectList = HenPlus.getInstance()
429: .getObjectLister();
430: final Iterator firstIter = objectList.completeTableName(
431: first, lastWord);
432: final Iterator secondIter = objectList.completeTableName(
433: second, lastWord);
434: final Iterator iter = getIntersection(firstIter, secondIter);
435: return new Iterator() {
436: String table = null;
437:
438: public boolean hasNext() {
439: while (iter.hasNext()) {
440: table = (String) iter.next();
441: if (alreadyGiven.contains(table)
442: && !lastWord.equals(table)) {
443: continue;
444: }
445: return true;
446: }
447: return false;
448: }
449:
450: public Object next() {
451: return table;
452: }
453:
454: public void remove() {
455: throw new UnsupportedOperationException("no!");
456: }
457: };
458: }
459: return null;
460: }
461:
462: private Iterator getIntersection(Iterator first, Iterator second) {
463: // first copy the first iterator into a list
464: List contentFirst = new ArrayList();
465: while (first.hasNext()) {
466: contentFirst.add(first.next());
467: }
468: // now copy all items of the second iterator into a second list
469: // which are contained in the first list
470: List inter = new ArrayList();
471: while (second.hasNext()) {
472: Object next = second.next();
473: if (contentFirst.contains(next)) {
474: inter.add(next);
475: }
476: }
477: return inter.iterator();
478: }
479:
480: private Iterator getSecondSessionCompleter(String lastWord,
481: final String firstSession) {
482: final Iterator it = HenPlus.getInstance().getSessionManager()
483: .completeSessionName(lastWord);
484: return new Iterator() {
485: String session = null;
486:
487: public boolean hasNext() {
488: while (it.hasNext()) {
489: session = (String) it.next();
490: if (session.equals(firstSession)) {
491: continue;
492: }
493: return true;
494: }
495: return false;
496: }
497:
498: public Object next() {
499: return session;
500: }
501:
502: public void remove() {
503: throw new UnsupportedOperationException("no!");
504: }
505: };
506: }
507:
508: /* (non-Javadoc)
509: * @see henplus.Command#isComplete(java.lang.String)
510: */
511: public boolean isComplete(String command) {
512: // HenPlus.msg().println( "[isComplete] command: " + command );
513: if (command.trim().endsWith(COMMAND_DELIMITER)) {
514: return true;
515: /*
516: StringTokenizer st = new StringTokenizer(command);
517: // we need at least four tokens.
518: final int minTokens = 4;
519: int count = 0;
520: while (st.hasMoreTokens() && count < minTokens) {
521: count++;
522: }
523: */
524: }
525: return false;
526: }
527:
528: /* (non-Javadoc)
529: * @see henplus.Command#requiresValidSession(java.lang.String)
530: */
531: public boolean requiresValidSession(String cmd) {
532: return false;
533: }
534:
535: /* (non-Javadoc)
536: * @see henplus.Command#shutdown()
537: */
538: public void shutdown() {
539: }
540:
541: /* (non-Javadoc)
542: * @see henplus.Command#getShortDescription()
543: */
544: public String getShortDescription() {
545: return "perform a diff on different tables";
546: }
547:
548: /* (non-Javadoc)
549: * @see henplus.Command#getSynopsis(java.lang.String)
550: */
551: public String getSynopsis(String cmd) {
552: return "\n"
553: + _command
554: + " <sessionname-1> <sessionname-2> (<tablename> | <prefix>* | *)+;\n"
555: + "or\n" + _command + " " + OPTION_SINGLE_DB
556: + " <table1> <table2>;\n";
557: }
558:
559: /* (non-Javadoc)
560: * @see henplus.Command#getLongDescription(java.lang.String)
561: */
562: public String getLongDescription(String cmd) {
563: return "\tCompare one or more tables by their meta data.\n"
564: + "\n"
565: + "\tThere are basically two use cases for comparing tables:\n"
566: + "\t1. Compare tables with equal names from different databases and\n"
567: + "\t2. Compare two tables with different names in the same database.\n"
568: + "\n"
569: + "\tFor the first use case you must specify two session names and one\n"
570: + "\tor more tables that exist in both sessions.\n"
571: + "\tYou are able to use wildcards (*) to match all tables or\n"
572: + "\ta specific set of tables.\n"
573: + "\tE.g. you might specify \"*\" to match all tables which are contained\n"
574: + "\tin both sessions, or\"tb_*\" to match all tables from your sessions\n"
575: + "\tstarting with \"tb_\".\n"
576: + "\n"
577: + "\tFor the second use case you must specifiy the option "
578: + OPTION_SINGLE_DB
579: + "\n"
580: + "\tand two tables.\n"
581: + "\n"
582: + "\tThe following is a list of compared column related\n"
583: + "\tproperties, with a \"c\" for a case sensitive and an \"i\" for\n"
584: + "\ta case insensitive comparision by default. If you\n"
585: + "\twonder what this is for, because you know that sql\n"
586: + "\tshould behave case insensitive, then ask your\n"
587: + "\tdatabase provider or the developer of the driver you use.\n"
588: + "\n"
589: + "\t - column name (i)\n"
590: + "\t - type (c)\n"
591: + "\t - nullable (-)\n"
592: + "\t - default value (c)\n"
593: + "\t - primary key definition (c)\n"
594: + "\t - foreign key definition (c).\n"
595: + "\n"
596: + "\tIn the future indices migth be added to the comparison,\n"
597: + "\tmoreover, an option \"o\" would be nice to get automatically\n"
598: + "\t\"ALTER TABLE ...\" scripts generated to a given output file.";
599: }
600: }
|