0001: /*
0002: * SchemaDiff.java
0003: *
0004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
0005: *
0006: * Copyright 2002-2008, Thomas Kellerer
0007: * No part of this code maybe reused without the permission of the author
0008: *
0009: * To contact the author please send an email to: support@sql-workbench.net
0010: *
0011: */
0012: package workbench.db.diff;
0013:
0014: import java.io.IOException;
0015: import java.io.Writer;
0016: import java.sql.SQLException;
0017: import java.util.ArrayList;
0018: import java.util.HashSet;
0019: import java.util.Iterator;
0020: import java.util.List;
0021: import workbench.db.DbMetadata;
0022: import workbench.db.DbSettings;
0023: import workbench.db.ProcedureDefinition;
0024: import workbench.db.SequenceDefinition;
0025: import workbench.db.SequenceReader;
0026: import workbench.db.TableIdentifier;
0027: import workbench.db.WbConnection;
0028: import workbench.db.report.ReportProcedure;
0029: import workbench.db.report.ReportSequence;
0030: import workbench.db.report.ReportTable;
0031: import workbench.db.report.ReportView;
0032: import workbench.db.report.TagWriter;
0033: import workbench.log.LogMgr;
0034: import workbench.resource.ResourceMgr;
0035: import workbench.storage.RowActionMonitor;
0036: import workbench.util.StrBuffer;
0037: import workbench.util.StrWriter;
0038: import workbench.util.StringUtil;
0039:
0040: /**
0041: * Compare two Schemas for differences in the definition of the tables
0042: *
0043: * @author support@sql-workbench.net
0044: */
0045: public class SchemaDiff {
0046: public static final String TAG_ADD_TABLE = "add-table";
0047: public static final String TAG_ADD_VIEW = "add-view";
0048: public static final String TAG_DROP_TABLE = "drop-table";
0049: public static final String TAG_DROP_VIEW = "drop-view";
0050: public static final String TAG_DROP_PROC = "drop-procedure";
0051: public static final String TAG_ADD_PROC = "add-procedure";
0052: public static final String TAG_DROP_SEQUENCE = "drop-sequence";
0053:
0054: public static final String TAG_REF_CONN = "reference-connection";
0055: public static final String TAG_TARGET_CONN = "target-connection";
0056: public static final String TAG_COMPARE_INFO = "compare-settings";
0057: public static final String TAG_VIEW_PAIR = "view-info";
0058: public static final String TAG_TABLE_PAIR = "table-info";
0059: public static final String TAG_PROC_PAIR = "procedure-info";
0060: public static final String TAG_INDEX_INFO = "include-index";
0061: public static final String TAG_FK_INFO = "include-foreign-key";
0062: public static final String TAG_PK_INFO = "include-primary-key";
0063: public static final String TAG_GRANT_INFO = "include-tablegrants";
0064: public static final String TAG_CONSTRAINT_INFO = "include-constraints";
0065: public static final String TAG_VIEW_INFO = "include-views";
0066: public static final String TAG_PROC_INFO = "include-procs";
0067: public static final String TAG_SEQUENCE_INFO = "include-sequences";
0068:
0069: private WbConnection sourceDb;
0070: private WbConnection targetDb;
0071: private List<Object> objectsToCompare;
0072: private List<TableIdentifier> tablesToDelete;
0073: private List<ProcedureDefinition> procsToDelete;
0074: private List<TableIdentifier> viewsToDelete;
0075: private List<SequenceDefinition> sequencesToDelete;
0076:
0077: private String namespace;
0078: private String encoding = "UTF-8";
0079: private boolean compareJdbcTypes = false;
0080: private boolean diffIndex = true;
0081: private boolean diffForeignKeys = true;
0082: private boolean diffPrimaryKeys = true;
0083: private boolean diffConstraints = false;
0084: private boolean diffGrants = false;
0085: private boolean diffViews = true;
0086: private boolean diffProcs = true;
0087: private boolean diffSequences = true;
0088:
0089: // private boolean diffComments;
0090: private RowActionMonitor monitor;
0091: private boolean cancel = false;
0092: private String referenceSchema;
0093: private String targetSchema;
0094: private List<String> tablesToIgnore;
0095:
0096: public SchemaDiff() {
0097: }
0098:
0099: /**
0100: * Create a new SchemaDiff for the given connections
0101: */
0102: public SchemaDiff(WbConnection source, WbConnection target) {
0103: this (source, target, null);
0104: }
0105:
0106: /**
0107: * Create a new SchemaDiff for the given connections with the given
0108: * namespace to be used when writing the XML.
0109: *
0110: * @param source The connection to the reference schema
0111: * @param target the connection to the target schema
0112: * @param xmlNameSpace the namespace to be used for the XML, may be null.
0113: */
0114: public SchemaDiff(WbConnection source, WbConnection target,
0115: String xmlNameSpace) {
0116: sourceDb = source;
0117: targetDb = target;
0118: this .namespace = xmlNameSpace;
0119: }
0120:
0121: public void setIncludeSequences(boolean flag) {
0122: this .diffSequences = flag;
0123: }
0124:
0125: public boolean getIncludeSequences() {
0126: return this .diffSequences;
0127: }
0128:
0129: /**
0130: * Control whether foreign keys should be compared as well.
0131: * The default is to compare foreign keys.
0132: */
0133: public void setIncludeForeignKeys(boolean flag) {
0134: this .diffForeignKeys = flag;
0135: }
0136:
0137: public boolean getIncludeForeignKeys() {
0138: return this .diffForeignKeys;
0139: }
0140:
0141: /**
0142: * Control whether index definitions should be compared as well.
0143: * The default is to compare index definitions
0144: */
0145: public void setIncludeIndex(boolean flag) {
0146: this .diffIndex = flag;
0147: }
0148:
0149: public boolean getIncludeIndex() {
0150: return this .diffIndex;
0151: }
0152:
0153: /**
0154: * Control whether primary keys should be compared as well.
0155: * The default is to compare primary keys.
0156: */
0157: public void setIncludePrimaryKeys(boolean flag) {
0158: this .diffPrimaryKeys = flag;
0159: }
0160:
0161: public boolean getIncludePrimaryKeys() {
0162: return this .diffPrimaryKeys;
0163: }
0164:
0165: /**
0166: * Control whether table constraints should be compared as well.
0167: * The default is to not compare primary keys.
0168: */
0169: public void setIncludeTableConstraints(boolean flag) {
0170: this .diffConstraints = flag;
0171: }
0172:
0173: public boolean getIncludeTableConstraints() {
0174: return this .diffConstraints;
0175: }
0176:
0177: public void setCompareJdbcTypes(boolean flag) {
0178: this .compareJdbcTypes = flag;
0179: }
0180:
0181: public boolean getCompareJdbcTypes() {
0182: return this .compareJdbcTypes;
0183: }
0184:
0185: public void setIncludeViews(boolean flag) {
0186: this .diffViews = flag;
0187: }
0188:
0189: public void setIncludeProcedures(boolean flag) {
0190: this .diffProcs = flag;
0191: }
0192:
0193: public void setIncludeTableGrants(boolean flag) {
0194: this .diffGrants = flag;
0195: }
0196:
0197: public boolean getIncludeTableGrants() {
0198: return this .diffGrants;
0199: }
0200:
0201: // public void setIncludeComments(boolean flag) { this.diffComments = flag; }
0202:
0203: /**
0204: * Set the {@link workbench.storage.RowActionMonitor} for reporting progress
0205: */
0206: public void setMonitor(RowActionMonitor mon) {
0207: this .monitor = mon;
0208: }
0209:
0210: /**
0211: * Cancel the creation of the XML file
0212: * @see #isCancelled()
0213: */
0214: public void cancel() {
0215: this .cancel = true;
0216: }
0217:
0218: /**
0219: * Return if the XML generation has been cancelled
0220: * @return true if #cancel() has been called
0221: */
0222: public boolean isCancelled() {
0223: return this .cancel;
0224: }
0225:
0226: /**
0227: * Define table names to be compared. The table names in the passed
0228: * lists will be converted to TableIdentifiers and then passed
0229: * on to setTables(List<TableIdentifier>, List<TableIdentifier>)
0230: *
0231: * No name matching will take place. Thus it's possible to compare
0232: * tables that might have different names but are supposed to be identical
0233: * otherwise.
0234: *
0235: * @see #setTables(List, List)
0236: * @see #compareAll()
0237: */
0238: public void setTableNames(List<String> referenceList,
0239: List<String> targetList) throws SQLException {
0240: ArrayList<TableIdentifier> reference = new ArrayList<TableIdentifier>(
0241: referenceList.size());
0242: ArrayList<TableIdentifier> target = new ArrayList<TableIdentifier>(
0243: targetList.size());
0244:
0245: String ttype = this .sourceDb.getMetadata().getTableTypeName();
0246: for (String tname : referenceList) {
0247: TableIdentifier tbl = new TableIdentifier(tname);
0248: tbl.setType(ttype);
0249: reference.add(tbl);
0250: }
0251:
0252: ttype = this .targetDb.getMetadata().getTableTypeName();
0253: for (String tname : targetList) {
0254: TableIdentifier tbl = new TableIdentifier(tname);
0255: tbl.setType(ttype);
0256: target.add(tbl);
0257: }
0258: setTables(reference, target);
0259: }
0260:
0261: /**
0262: * Define the tables to be compared. They will be compared based
0263: * on the position in the arrays (i.e. reference at index 0 will be
0264: * compared to target at index 0...)
0265: *
0266: * No name matching will take place. Thus it's possible to compare
0267: * tables that might have different names but are supposed to be identical
0268: * otherwise.
0269: *
0270: * @see #setTables(List)
0271: * @see #compareAll()
0272: */
0273: public void setTables(List<TableIdentifier> referenceList,
0274: List<TableIdentifier> targetList) throws SQLException {
0275: if (referenceList == null)
0276: throw new NullPointerException(
0277: "Source tables may not be null");
0278: if (targetList == null)
0279: throw new NullPointerException(
0280: "Target tables may not be null");
0281: if (referenceList.size() != targetList.size())
0282: throw new IllegalArgumentException(
0283: "Number of source and target tables have to match");
0284: int count = referenceList.size();
0285: this .objectsToCompare = new ArrayList<Object>(count);
0286:
0287: if (this .monitor != null) {
0288: this .monitor
0289: .setMonitorType(RowActionMonitor.MONITOR_PROCESS_TABLE);
0290: this .monitor.setCurrentObject(ResourceMgr
0291: .getString("MsgDiffRetrieveDbInfo"), -1, -1);
0292: }
0293:
0294: for (int i = 0; i < count; i++) {
0295: if (this .cancel) {
0296: this .objectsToCompare = null;
0297: break;
0298: }
0299: TableIdentifier reference = referenceList.get(i);
0300:
0301: if (reference == null)
0302: continue;
0303:
0304: TableIdentifier target = targetList.get(i);
0305: DiffEntry entry = new DiffEntry(reference, target);
0306: this .objectsToCompare.add(entry);
0307: }
0308: }
0309:
0310: private ReportView createReportViewInstance(TableIdentifier tbl,
0311: WbConnection con) throws SQLException {
0312: tbl.adjustCase(con);
0313: ReportView view = new ReportView(tbl, con, diffIndex,
0314: this .namespace);
0315: return view;
0316: }
0317:
0318: private ReportTable createReportTableInstance(TableIdentifier tbl,
0319: WbConnection con) throws SQLException {
0320: tbl.adjustCase(con);
0321: return new ReportTable(tbl, con, this .namespace, diffIndex,
0322: diffForeignKeys, diffPrimaryKeys, diffConstraints,
0323: diffGrants);
0324: }
0325:
0326: /**
0327: * Define a list of table names that should not be compared.
0328: * Tables in the reference/source database that match one of the
0329: * names in this list will be skipped.
0330: */
0331: public void setExcludeTables(List<String> tables) {
0332: if (tables == null || tables.size() == 0) {
0333: this .tablesToIgnore = null;
0334: return;
0335: }
0336: int count = tables.size();
0337: this .tablesToIgnore = new ArrayList<String>(count);
0338: for (String tname : tables) {
0339: this .tablesToIgnore.add(this .sourceDb.getMetadata()
0340: .adjustObjectnameCase(tname));
0341: }
0342: }
0343:
0344: /**
0345: * Setup this SchemaDiff object to compare all tables that the user
0346: * can access in the reference connection with all matching (=same name)
0347: * tables in the target connection.
0348: * This will retrieve all user tables from the reference (=source)
0349: * connection and will match them to the tables in the target connection.
0350: *
0351: * When using compareAll() drop statements will be created for tables
0352: * present in the target connection but not existing in the reference
0353: * connection.
0354: *
0355: * @see #setTables(List, List)
0356: * @see #setTables(List)
0357: */
0358: public void compareAll() throws SQLException {
0359: setSchemas(null, null);
0360: }
0361:
0362: /**
0363: * Setup this SchemaDiff object to compare all tables that the user
0364: * can access in the reference connection with all matching (=same name)
0365: * tables in the target connection.
0366: * This will retrieve all user tables from the reference (=source)
0367: * connection and will match them to the tables in the target connection.
0368: *
0369: * When using compareAll() drop statements will be created for tables
0370: * present in the target connection but not existing in the reference
0371: * connection.
0372: *
0373: * @see #setTables(List, List)
0374: * @see #setTables(List)
0375: */
0376: public void setSchemas(String rSchema, String tSchema)
0377: throws SQLException {
0378: if (this .monitor != null) {
0379: this .monitor.setMonitorType(RowActionMonitor.MONITOR_PLAIN);
0380: this .monitor.setCurrentObject(ResourceMgr
0381: .getString("MsgDiffRetrieveDbInfo"), -1, -1);
0382: }
0383: this .referenceSchema = (rSchema == null ? this .sourceDb
0384: .getMetadata().getSchemaToUse() : this .sourceDb
0385: .getMetadata().adjustSchemaNameCase(rSchema));
0386: this .targetSchema = (tSchema == null ? this .targetDb
0387: .getMetadata().getSchemaToUse() : this .sourceDb
0388: .getMetadata().adjustSchemaNameCase(tSchema));
0389:
0390: String[] types;
0391: if (diffViews) {
0392: types = new String[] {
0393: this .sourceDb.getMetadata().getTableTypeName(),
0394: this .sourceDb.getMetadata().getViewTypeName() };
0395: } else {
0396: types = new String[] { this .sourceDb.getMetadata()
0397: .getTableTypeName() };
0398: }
0399: List<TableIdentifier> refTables = sourceDb.getMetadata()
0400: .getTableList(this .referenceSchema, types);
0401: List<TableIdentifier> target = targetDb.getMetadata()
0402: .getTableList(this .targetSchema, types);
0403:
0404: processTableList(refTables, target);
0405:
0406: if (diffProcs) {
0407: List<ProcedureDefinition> refProcs = sourceDb.getMetadata()
0408: .getProcedureList(null, this .referenceSchema);
0409: List<ProcedureDefinition> targetProcs = targetDb
0410: .getMetadata().getProcedureList(null,
0411: this .targetSchema);
0412: processProcedureList(refProcs, targetProcs);
0413: }
0414:
0415: if (diffSequences) {
0416: SequenceReader refReader = sourceDb.getMetadata()
0417: .getSequenceReader();
0418: List<SequenceDefinition> refSeqs = refReader
0419: .getSequences(this .referenceSchema);
0420: SequenceReader tarReader = targetDb.getMetadata()
0421: .getSequenceReader();
0422: List<SequenceDefinition> tarSeqs = tarReader
0423: .getSequences(this .referenceSchema);
0424: processSequenceList(refSeqs, tarSeqs);
0425: }
0426: }
0427:
0428: private void processTableList(List<TableIdentifier> refTables,
0429: List<TableIdentifier> targetTables) throws SQLException {
0430: int count = refTables.size();
0431: HashSet<String> refTableNames = new HashSet<String>();
0432:
0433: this .objectsToCompare = new ArrayList<Object>(count);
0434: DbMetadata targetMeta = this .targetDb.getMetadata();
0435:
0436: if (this .monitor != null) {
0437: this .monitor.setMonitorType(RowActionMonitor.MONITOR_PLAIN);
0438: }
0439:
0440: for (int i = 0; i < count; i++) {
0441: if (this .cancel) {
0442: this .objectsToCompare = null;
0443: break;
0444: }
0445:
0446: TableIdentifier rid = refTables.get(i);
0447:
0448: // The table names to be excluded have been put into
0449: // the list after calling adjustObjectnameCase() on the input values
0450: // so we have to apply the same logic here.
0451: String tname = StringUtil.trimQuotes(rid.getTableName());
0452: tname = this .sourceDb.getMetadata().adjustObjectnameCase(
0453: tname);
0454: if (this .tablesToIgnore != null
0455: && this .tablesToIgnore.contains(tname))
0456: continue;
0457:
0458: if (this .monitor != null) {
0459: this .monitor.setCurrentObject(ResourceMgr
0460: .getFormattedString("MsgLoadTableInfo", tname),
0461: -1, -1);
0462: }
0463:
0464: TableIdentifier tid = rid.createCopy();
0465: tid.setSchema(this .targetSchema);
0466:
0467: DiffEntry entry = null;
0468: if (targetMeta.objectExists(tid, rid.getType())) {
0469: tid.setType(rid.getType());
0470: entry = new DiffEntry(rid, tid);
0471: } else {
0472: entry = new DiffEntry(rid, null);
0473: }
0474: objectsToCompare.add(entry);
0475: refTableNames.add(tname);
0476: }
0477:
0478: if (cancel)
0479: return;
0480:
0481: this .tablesToDelete = new ArrayList<TableIdentifier>();
0482: this .viewsToDelete = new ArrayList<TableIdentifier>();
0483:
0484: if (targetTables != null) {
0485: String tableType = targetDb.getMetadata()
0486: .getTableTypeName();
0487: count = targetTables.size();
0488: for (int i = 0; i < count; i++) {
0489: TableIdentifier t = targetTables.get(i);
0490: String tbl = StringUtil.trimQuotes(t.getTableName());
0491: if (this .tablesToIgnore != null
0492: && this .tablesToIgnore.contains(tbl))
0493: continue;
0494:
0495: if (targetDb.getMetadata().isDefaultCase(tbl)) {
0496: tbl = sourceDb.getMetadata().adjustObjectnameCase(
0497: tbl);
0498: }
0499: if (!refTableNames.contains(tbl)) {
0500: if (tableType.equals(t.getType())) {
0501: this .tablesToDelete.add(t);
0502: } else {
0503: this .viewsToDelete.add(t);
0504: }
0505: }
0506: }
0507: }
0508: }
0509:
0510: private void processSequenceList(List<SequenceDefinition> refSeqs,
0511: List<SequenceDefinition> targetSeqs) {
0512: HashSet<String> refSeqNames = new HashSet<String>();
0513: this .sequencesToDelete = new ArrayList<SequenceDefinition>();
0514:
0515: if (this .monitor != null) {
0516: this .monitor.setMonitorType(RowActionMonitor.MONITOR_PLAIN);
0517: }
0518:
0519: SequenceReader refReader = this .sourceDb.getMetadata()
0520: .getSequenceReader();
0521: SequenceReader targetReader = this .targetDb.getMetadata()
0522: .getSequenceReader();
0523:
0524: for (SequenceDefinition refSeq : refSeqs) {
0525: if (this .cancel) {
0526: this .objectsToCompare = null;
0527: break;
0528: }
0529:
0530: if (this .monitor != null) {
0531: this .monitor.setCurrentObject(ResourceMgr
0532: .getFormattedString("MsgLoadSeqInfo", refSeq
0533: .getSequenceName()), -1, -1);
0534: }
0535:
0536: SequenceDiffEntry entry = null;
0537: SequenceDefinition def = targetReader
0538: .getSequenceDefinition(this .targetSchema, refSeq
0539: .getSequenceName());
0540: entry = new SequenceDiffEntry(refSeq, def);
0541: objectsToCompare.add(entry);
0542: refSeqNames.add(refSeq.getSequenceName());
0543: }
0544:
0545: if (cancel)
0546: return;
0547:
0548: if (targetSeqs != null) {
0549: for (SequenceDefinition tSeq : targetSeqs) {
0550: String seqname = tSeq.getSequenceName();
0551: if (!refSeqNames.contains(seqname)) {
0552: this .sequencesToDelete.add(tSeq);
0553: }
0554: }
0555: }
0556:
0557: }
0558:
0559: private void processProcedureList(
0560: List<ProcedureDefinition> refProcs,
0561: List<ProcedureDefinition> targetProcs) {
0562: HashSet<String> refProcNames = new HashSet<String>();
0563: this .procsToDelete = new ArrayList<ProcedureDefinition>();
0564:
0565: DbMetadata targetMeta = this .targetDb.getMetadata();
0566:
0567: this .monitor.setMonitorType(RowActionMonitor.MONITOR_PLAIN);
0568:
0569: for (ProcedureDefinition refProc : refProcs) {
0570: if (this .cancel) {
0571: this .objectsToCompare = null;
0572: break;
0573: }
0574:
0575: if (this .monitor != null) {
0576: this .monitor.setCurrentObject(ResourceMgr
0577: .getFormattedString("MsgLoadProcInfo", refProc
0578: .getProcedureName()), -1, -1);
0579: }
0580:
0581: ProcDiffEntry entry = null;
0582: ProcedureDefinition tp = new ProcedureDefinition(null,
0583: this .targetSchema, refProc.getProcedureName(),
0584: refProc.getResultType());
0585: if (targetMeta.procedureExists(tp)) {
0586: entry = new ProcDiffEntry(refProc, tp);
0587: } else {
0588: entry = new ProcDiffEntry(refProc, null);
0589: }
0590: objectsToCompare.add(entry);
0591: refProcNames.add(refProc.getProcedureName());
0592: }
0593:
0594: if (cancel)
0595: return;
0596:
0597: if (targetProcs != null) {
0598: for (ProcedureDefinition tProc : targetProcs) {
0599: String procname = tProc.getProcedureName();
0600: if (!refProcNames.contains(procname)) {
0601: this .procsToDelete.add(tProc);
0602: }
0603: }
0604: }
0605: }
0606:
0607: /**
0608: * Define the reference tables to be compared with the matching
0609: * tables (based on the name) in the target connection. The list
0610: * has to contain objects of type {@link workbench.db.TableIdentifier}
0611: *
0612: * @see #setTables(List, List)
0613: * @see #compareAll()
0614: */
0615: public void setTables(List<TableIdentifier> reference)
0616: throws SQLException {
0617: this .processTableList(reference, null);
0618: }
0619:
0620: /**
0621: * Return the XML that describes how the target schema needs to be
0622: * modified in order to get the same structure as the reference schema.
0623: *
0624: * For this, each defined table in the reference schema will be compared
0625: * to the corresponding table in the target schema.
0626: *
0627: * @see TableDiff#getMigrateTargetXml()
0628: */
0629: public String getMigrateTargetXml() {
0630: StrWriter writer = new StrWriter(5000);
0631: try {
0632: this .writeXml(writer);
0633: } catch (Exception e) {
0634: LogMgr.logError("SchemaDiff.getMigrateTargetXml()",
0635: "Error getting XML", e);
0636: }
0637: return writer.toString();
0638: }
0639:
0640: /**
0641: * Return the encoding that is used in the encoding attribute of the XML tag
0642: */
0643: public String getEncoding() {
0644: return encoding;
0645: }
0646:
0647: /**
0648: * Set the encoding that is used for writing the XML. This will
0649: * be put into the <?xml tag at the beginning of the generated XML
0650: */
0651: public void setEncoding(String encoding) {
0652: this .encoding = encoding;
0653: }
0654:
0655: /**
0656: * Write the XML of the schema differences to the supplied writer.
0657: * This writes some meta information about the compare, and then
0658: * creates a {@link TableDiff} object for each pair of tables that
0659: * needs to be compared. The output of {@link TableDiff#getMigrateTargetXml()}
0660: * will then be written into the writer.
0661: */
0662: public void writeXml(Writer out) throws IOException {
0663: if (objectsToCompare == null)
0664: throw new NullPointerException(
0665: "Source tables may not be null");
0666:
0667: StrBuffer indent = new StrBuffer(" ");
0668: StrBuffer tblIndent = new StrBuffer(" ");
0669: TagWriter tw = new TagWriter(this .namespace);
0670: out.write("<?xml version=\"1.0\" encoding=\"");
0671: out.write(this .encoding);
0672: out.write("\"?>\n");
0673:
0674: if (this .monitor != null) {
0675: this .monitor
0676: .setMonitorType(RowActionMonitor.MONITOR_PROCESS_TABLE);
0677: }
0678:
0679: writeTag(out, null, "schema-diff", true);
0680: writeDiffInfo(out);
0681: int count = this .objectsToCompare.size();
0682: List<ViewDiff> viewDiffs = new ArrayList<ViewDiff>();
0683: String tableType = sourceDb.getMetadata().getTableTypeName();
0684: // First we have to process the tables
0685: for (int i = 0; i < count; i++) {
0686: Object o = objectsToCompare.get(i);
0687: if (o instanceof ProcDiffEntry)
0688: continue;
0689: if (o instanceof SequenceDiffEntry)
0690: continue;
0691:
0692: DiffEntry entry = (DiffEntry) o;
0693: if (this .cancel)
0694: break;
0695:
0696: if (this .monitor != null) {
0697: this .monitor.setCurrentObject(entry.reference
0698: .getTableExpression(), i + 1, count);
0699: }
0700:
0701: try {
0702: if (tableType.equalsIgnoreCase(entry.reference
0703: .getType())) {
0704: ReportTable source = createReportTableInstance(
0705: entry.reference, this .sourceDb);
0706: if (entry.target == null) {
0707: out.write("\n");
0708: writeTag(out, indent, TAG_ADD_TABLE, true,
0709: "name", entry.reference.getTableName());
0710: StrBuffer s = source.getXml(tblIndent);
0711: s.writeTo(out);
0712: writeTag(out, indent, TAG_ADD_TABLE, false);
0713: } else {
0714: ReportTable target = createReportTableInstance(
0715: entry.target, this .targetDb);
0716: TableDiff d = new TableDiff(source, target,
0717: this );
0718: //d.setCompareComments(this.diffComments);
0719: d.setIndent(indent);
0720: d.setTagWriter(tw);
0721: StrBuffer s = d.getMigrateTargetXml();
0722: if (s.length() > 0) {
0723: out.write("\n");
0724: s.writeTo(out);
0725: }
0726: }
0727: } else {
0728: // We cannot write out the diff for the views immediately
0729: // because they should be listed after the table diffs
0730: ReportView source = createReportViewInstance(
0731: entry.reference, sourceDb);
0732: ReportView target = null;
0733: if (entry.target != null) {
0734: target = createReportViewInstance(entry.target,
0735: targetDb);
0736: }
0737: ViewDiff d = new ViewDiff(source, target);
0738: d.setIndent(indent);
0739: d.setTagWriter(tw);
0740: viewDiffs.add(d);
0741: }
0742: } catch (SQLException sql) {
0743: LogMgr.logError("SchemaDiff.writeXml()",
0744: "Error comparing " + entry.toString(), sql);
0745: }
0746: }
0747:
0748: if (this .cancel)
0749: return;
0750:
0751: this .appendDropTables(out, indent);
0752: out.write("\n");
0753:
0754: if (this .diffViews) {
0755: this .appendViewDiff(viewDiffs, out);
0756: //out.write("\n");
0757: this .appendDropViews(out, indent, tw);
0758: }
0759: if (this .diffSequences) {
0760: this .appendSequenceDiff(out, indent, tw);
0761: out.write("\n");
0762: }
0763:
0764: if (this .diffProcs) {
0765: this .appendProcDiff(out, indent, tw);
0766: out.write("\n");
0767: }
0768:
0769: if (this .cancel)
0770: return;
0771:
0772: if (this .diffProcs) {
0773: this .appendProcDiff(out, indent, tw);
0774: out.write("\n");
0775: }
0776:
0777: writeTag(out, null, "schema-diff", false);
0778: }
0779:
0780: private void appendSequenceDiff(Writer out, StrBuffer indent,
0781: TagWriter tw) throws IOException {
0782: int count = this .objectsToCompare.size();
0783: for (int i = 0; i < count; i++) {
0784: Object o = objectsToCompare.get(i);
0785: if (o instanceof DiffEntry)
0786: continue;
0787: if (o instanceof ProcDiffEntry)
0788: continue;
0789:
0790: SequenceDiffEntry entry = (SequenceDiffEntry) o;
0791: ReportSequence rp = new ReportSequence(entry.reference,
0792: this .namespace);
0793: ReportSequence tp = (entry.target == null ? null
0794: : new ReportSequence(entry.target, this .namespace));
0795: SequenceDiff diff = new SequenceDiff(rp, tp);
0796: diff.setIndent(indent);
0797: diff.setTagWriter(tw);
0798: StrBuffer xml = diff.getMigrateTargetXml();
0799: if (xml.length() > 0) {
0800: out.write("\n");
0801: xml.writeTo(out);
0802: }
0803: }
0804:
0805: if (this .sequencesToDelete == null
0806: || sequencesToDelete.size() == 0)
0807: return;
0808:
0809: out.write('\n');
0810: writeTag(out, indent, TAG_DROP_SEQUENCE, true);
0811: StrBuffer myindent = new StrBuffer(indent);
0812: myindent.append(" ");
0813: Iterator itr = this .sequencesToDelete.iterator();
0814: for (SequenceDefinition def : sequencesToDelete) {
0815: writeTagValue(out, myindent, ReportSequence.TAG_SEQ_NAME,
0816: def.getSequenceName());
0817: }
0818: writeTag(out, indent, TAG_DROP_SEQUENCE, false);
0819: }
0820:
0821: private void appendProcDiff(Writer out, StrBuffer indent,
0822: TagWriter tw) throws IOException {
0823: int count = this .objectsToCompare.size();
0824: for (int i = 0; i < count; i++) {
0825: Object o = objectsToCompare.get(i);
0826: if (o instanceof DiffEntry)
0827: continue;
0828: if (o instanceof SequenceDiffEntry)
0829: continue;
0830:
0831: ProcDiffEntry entry = (ProcDiffEntry) o;
0832: ReportProcedure rp = new ReportProcedure(entry.reference,
0833: this .sourceDb);
0834: ReportProcedure tp = new ReportProcedure(entry.target,
0835: this .targetDb);
0836: ProcDiff diff = new ProcDiff(rp, tp);
0837: diff.setIndent(indent);
0838: diff.setTagWriter(tw);
0839: StrBuffer xml = diff.getMigrateTargetXml();
0840: if (xml.length() > 0) {
0841: out.write("\n");
0842: xml.writeTo(out);
0843: }
0844: }
0845:
0846: if (this .procsToDelete == null || procsToDelete.size() == 0)
0847: return;
0848:
0849: out.write('\n');
0850: writeTag(out, indent, TAG_DROP_PROC, true);
0851: StrBuffer myindent = new StrBuffer(indent);
0852: myindent.append(" ");
0853: Iterator itr = this .procsToDelete.iterator();
0854: while (itr.hasNext()) {
0855: ProcedureDefinition def = (ProcedureDefinition) itr.next();
0856: ReportProcedure rp = new ReportProcedure(def, targetDb);
0857: rp.setIndent(myindent);
0858: StrBuffer xml = rp.getXml(false);
0859: xml.writeTo(out);
0860: }
0861: writeTag(out, indent, TAG_DROP_PROC, false);
0862: }
0863:
0864: private void appendViewDiff(List<ViewDiff> diffs, Writer out)
0865: throws IOException {
0866: for (ViewDiff d : diffs) {
0867: StrBuffer source = d.getMigrateTargetXml();
0868: if (source.length() > 0) {
0869: out.write("\n");
0870: source.writeTo(out);
0871: }
0872: }
0873: }
0874:
0875: private void appendDropViews(Writer out, StrBuffer indent,
0876: TagWriter tw) throws IOException {
0877: if (this .viewsToDelete == null
0878: || this .viewsToDelete.size() == 0)
0879: return;
0880: out.write("\n");
0881: writeTag(out, indent, TAG_DROP_VIEW, true);
0882: Iterator itr = this .viewsToDelete.iterator();
0883: StrBuffer myindent = new StrBuffer(indent);
0884: myindent.append(" ");
0885: while (itr.hasNext()) {
0886: TableIdentifier t = (TableIdentifier) itr.next();
0887: writeTagValue(out, myindent, ReportView.TAG_VIEW_NAME, t
0888: .getTableName());
0889: }
0890: writeTag(out, indent, TAG_DROP_VIEW, false);
0891: }
0892:
0893: private void appendDropTables(Writer out, StrBuffer indent)
0894: throws IOException {
0895: if (this .tablesToDelete == null
0896: || this .tablesToDelete.size() == 0)
0897: return;
0898: out.write("\n");
0899: writeTag(out, indent, TAG_DROP_TABLE, true);
0900: Iterator itr = this .tablesToDelete.iterator();
0901: StrBuffer myindent = new StrBuffer(indent);
0902: myindent.append(" ");
0903: while (itr.hasNext()) {
0904: TableIdentifier t = (TableIdentifier) itr.next();
0905: writeTagValue(out, myindent, ReportTable.TAG_TABLE_NAME, t
0906: .getTableName());
0907: }
0908: writeTag(out, indent, TAG_DROP_TABLE, false);
0909: }
0910:
0911: private void writeDiffInfo(Writer out) throws IOException {
0912: StrBuffer indent = new StrBuffer(" ");
0913: StrBuffer indent2 = new StrBuffer(" ");
0914: writeTag(out, indent, TAG_REF_CONN, true);
0915: StrBuffer info = this .sourceDb.getDatabaseInfoAsXml(indent2,
0916: this .namespace);
0917: info.writeTo(out);
0918: writeTag(out, indent, TAG_REF_CONN, false);
0919: out.write("\n");
0920: out
0921: .write(" <!-- If the target connection is modified according to the -->\n");
0922: out
0923: .write(" <!-- defintions in this file, then its structure will be -->\n");
0924: out.write(" <!-- the same as the reference connection -->\n");
0925: writeTag(out, indent, TAG_TARGET_CONN, true);
0926: info = this .targetDb.getDatabaseInfoAsXml(indent2,
0927: this .namespace);
0928: info.writeTo(out);
0929: writeTag(out, indent, TAG_TARGET_CONN, false);
0930: out.write("\n");
0931:
0932: info = new StrBuffer();
0933: TagWriter tw = new TagWriter(this .namespace);
0934:
0935: tw.appendOpenTag(info, indent, TAG_COMPARE_INFO);
0936: info.append('\n');
0937: tw.appendTag(info, indent2, TAG_INDEX_INFO, this .diffIndex);
0938: tw.appendTag(info, indent2, TAG_FK_INFO, this .diffForeignKeys);
0939: tw.appendTag(info, indent2, TAG_PK_INFO, this .diffPrimaryKeys);
0940: tw.appendTag(info, indent2, TAG_CONSTRAINT_INFO,
0941: this .diffConstraints);
0942: tw.appendTag(info, indent2, TAG_GRANT_INFO, this .diffGrants);
0943: tw.appendTag(info, indent2, TAG_VIEW_INFO, this .diffViews);
0944:
0945: if (this .referenceSchema != null && this .targetSchema != null) {
0946: tw.appendTag(info, indent2, "reference-schema",
0947: this .referenceSchema);
0948: tw.appendTag(info, indent2, "target-schema",
0949: this .targetSchema);
0950: }
0951: int count = this .objectsToCompare.size();
0952: String tattr[] = new String[] { "type", "reference",
0953: "compareTo" };
0954: String pattr[] = new String[] { "referenceProcedure",
0955: "compareTo" };
0956: String tbls[] = new String[3];
0957: DbSettings dbs = this .sourceDb.getMetadata().getDbSettings();
0958: for (int i = 0; i < count; i++) {
0959: // check for ignored tables
0960: //if (this.referenceTables[i] == null) continue;
0961: Object o = objectsToCompare.get(i);
0962: if (o instanceof DiffEntry) {
0963: DiffEntry de = (DiffEntry) o;
0964: tbls[0] = de.reference.getType();
0965: tbls[1] = (de.target == null ? "" : StringUtil
0966: .trimQuotes(de.target.getTableName()));
0967: tbls[2] = StringUtil.trimQuotes(de.reference
0968: .getTableName());
0969: if (dbs.isViewType(tbls[0])) {
0970: tw.appendOpenTag(info, indent2, TAG_VIEW_PAIR,
0971: tattr, tbls, false);
0972: } else {
0973: tw.appendOpenTag(info, indent2, TAG_TABLE_PAIR,
0974: tattr, tbls, false);
0975: }
0976: } else if (o instanceof ProcDiffEntry) {
0977: ProcDiffEntry pe = (ProcDiffEntry) o;
0978: tbls[0] = pe.reference.getProcedureName();
0979: tbls[1] = (pe.target == null ? "" : pe.target
0980: .getProcedureName());
0981:
0982: tw.appendOpenTag(info, indent2, TAG_PROC_PAIR, pattr,
0983: tbls, false);
0984: } else if (o instanceof SequenceDiffEntry) {
0985: SequenceDiffEntry pe = (SequenceDiffEntry) o;
0986: tbls[0] = pe.reference.getSequenceName();
0987: tbls[1] = (pe.target == null ? "" : pe.target
0988: .getSequenceName());
0989: tw.appendOpenTag(info, indent2, TAG_PROC_PAIR, pattr,
0990: tbls, false);
0991: }
0992: info.append("/>\n");
0993:
0994: }
0995: tw.appendCloseTag(info, indent, TAG_COMPARE_INFO);
0996:
0997: info.writeTo(out);
0998: }
0999:
1000: private void writeTag(Writer out, StrBuffer indent, String tag,
1001: boolean isOpeningTag) throws IOException {
1002: writeTag(out, indent, tag, isOpeningTag, null, null);
1003: }
1004:
1005: private void writeTag(Writer out, StrBuffer indent, String tag,
1006: boolean isOpeningTag, String attr, String attrValue)
1007: throws IOException {
1008: if (indent != null)
1009: indent.writeTo(out);
1010: if (isOpeningTag) {
1011: out.write("<");
1012: } else {
1013: out.write("</");
1014: }
1015: if (this .namespace != null) {
1016: out.write(namespace);
1017: out.write(":");
1018: }
1019: out.write(tag);
1020: if (isOpeningTag && attr != null) {
1021: out.write(' ');
1022: out.write(attr);
1023: out.write("=\"");
1024: out.write(attrValue);
1025: out.write('"');
1026: }
1027: out.write(">\n");
1028: }
1029:
1030: private void writeTagValue(Writer out, StrBuffer indent,
1031: String tag, String value) throws IOException {
1032: if (indent != null)
1033: indent.writeTo(out);
1034: out.write("<");
1035: if (this .namespace != null) {
1036: out.write(namespace);
1037: out.write(":");
1038: }
1039: out.write(tag);
1040: out.write(">");
1041: out.write(value);
1042: out.write("</");
1043: if (this .namespace != null) {
1044: out.write(namespace);
1045: out.write(":");
1046: }
1047: out.write(tag);
1048: out.write(">\n");
1049: }
1050: }
1051:
1052: class SequenceDiffEntry {
1053: SequenceDefinition reference;
1054: SequenceDefinition target;
1055:
1056: public SequenceDiffEntry(SequenceDefinition ref,
1057: SequenceDefinition tar) {
1058: reference = ref;
1059: target = tar;
1060: }
1061: }
1062:
1063: class ProcDiffEntry {
1064: ProcedureDefinition reference;
1065: ProcedureDefinition target;
1066:
1067: public ProcDiffEntry(ProcedureDefinition ref,
1068: ProcedureDefinition tar) {
1069: reference = ref;
1070: target = tar;
1071: }
1072: }
1073:
1074: class DiffEntry {
1075: TableIdentifier reference;
1076: TableIdentifier target;
1077:
1078: public DiffEntry(TableIdentifier ref, TableIdentifier tar) {
1079: reference = ref;
1080: target = tar;
1081: }
1082:
1083: public String toString() {
1084: if (target == null)
1085: return reference.getType() + ": "
1086: + reference.getTableExpression();
1087: else
1088: return reference.getType() + ": "
1089: + reference.getTableExpression() + " to "
1090: + target.getType() + ": "
1091: + target.getTableExpression();
1092: }
1093: }
|