001: /*
002: * TableDiff.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.db.diff;
013:
014: import java.util.ArrayList;
015: import java.util.Collection;
016: import java.util.Iterator;
017: import java.util.List;
018: import workbench.db.IndexDefinition;
019: import workbench.db.TableIdentifier;
020: import workbench.db.report.ReportColumn;
021: import workbench.db.report.ReportTable;
022: import workbench.db.report.ReportTableGrants;
023: import workbench.db.report.TagWriter;
024: import workbench.util.StrBuffer;
025: import workbench.util.StringUtil;
026:
027: /**
028: * Compares and evaluates the difference between a reference table
029: * and a target table.
030: * Comparing the columns is delegated to {@link ColumnDiff}
031: *
032: * @author support@sql-workbench.net
033: */
034: public class TableDiff {
035: public static final String TAG_RENAME_TABLE = "rename";
036: public static final String TAG_MODIFY_TABLE = "modify-table";
037: public static final String TAG_ADD_COLUMN = "add-column";
038: public static final String TAG_REMOVE_COLUMN = "remove-column";
039: public static final String TAG_ADD_PK = "add-primary-key";
040: public static final String TAG_MODIFY_PK = "modify-primary-key";
041: public static final String TAG_REMOVE_PK = "remove-primary-key";
042: public static final String TAG_TABLE_CONS = "table-constraint";
043:
044: private ReportTable referenceTable;
045: private ReportTable targetTable;
046: private StrBuffer indent;
047: private TagWriter writer;
048: private SchemaDiff diff;
049:
050: public TableDiff(ReportTable reference, ReportTable target,
051: SchemaDiff factory) {
052: if (reference == null)
053: throw new NullPointerException(
054: "Reference table may not be null");
055: if (target == null)
056: throw new NullPointerException(
057: "Target table may not be null");
058: this .referenceTable = reference;
059: this .targetTable = target;
060: this .diff = factory;
061: }
062:
063: /**
064: * Return the XML that describes how the target table needs to
065: * modified in order to get the same structure as the reference table.
066: * An empty string means that there are no differences
067: */
068: public StrBuffer getMigrateTargetXml() {
069: StrBuffer result = new StrBuffer(500);
070: TableIdentifier ref = this .referenceTable.getTable();
071: TableIdentifier target = this .targetTable.getTable();
072: if (this .writer == null)
073: this .writer = new TagWriter();
074: StrBuffer colDiff = new StrBuffer(500);
075: ArrayList<ReportColumn> colsToBeAdded = new ArrayList<ReportColumn>();
076: ReportColumn[] refCols = this .referenceTable.getColumns();
077: StrBuffer myindent = new StrBuffer(indent);
078: myindent.append(" ");
079:
080: for (int i = 0; i < refCols.length; i++) {
081: ReportColumn tcol = targetTable.findColumn(refCols[i]
082: .getColumn().getColumnName());
083: if (tcol == null) {
084: colsToBeAdded.add(refCols[i]);
085: } else {
086: ColumnDiff d = new ColumnDiff(refCols[i], tcol);
087: //d.setCompareComments(this.compareComments);
088: d.setCompareForeignKeys(this .diff
089: .getIncludeForeignKeys());
090: d.setCompareJdbcTypes(diff.getCompareJdbcTypes());
091: d.setTagWriter(this .writer);
092: d.setIndent(myindent);
093: StrBuffer diffXml = d.getMigrateTargetXml();
094: if (diffXml.length() > 0) {
095: colDiff.append(diffXml);
096: //colDiff.append('\n');
097: }
098: }
099: }
100: ArrayList<ReportColumn> colsToBeRemoved = new ArrayList<ReportColumn>();
101: ReportColumn[] tcols = this .targetTable.getColumns();
102: for (int i = 0; i < tcols.length; i++) {
103: if (this .referenceTable.findColumn(tcols[i].getColumn()
104: .getColumnName()) == null) {
105: colsToBeRemoved.add(tcols[i]);
106: }
107: }
108:
109: // boolean commentsEqual = true;
110: // if (this.compareComments)
111: // {
112: // commentsEqual = StringUtil.equalString(this.referenceTable.getTableComment(), this.targetTable.getTableComment());
113: // }
114:
115: String refname = ref.getTableName();
116: String tname = target.getTableName();
117: boolean rename = false;
118:
119: // If either one of the table names is quoted
120: // we have to do a case-sensitiv comparison
121: if (refname.charAt(0) == '\"' || tname.charAt(0) == '\"') {
122: refname = StringUtil.trimQuotes(refname);
123: tname = StringUtil.trimQuotes(tname);
124: rename = !refname.equals(tname);
125: } else {
126: rename = !refname.equalsIgnoreCase(tname);
127: }
128:
129: String rc = this .referenceTable.getTableConstraints();
130: String tc = this .targetTable.getTableConstraints();
131:
132: boolean constraintsEqual = StringUtil.equalString(rc, tc);
133:
134: List<String> refPk = this .referenceTable.getPrimaryKeyColumns();
135: List<String> tPk = this .targetTable.getPrimaryKeyColumns();
136:
137: StrBuffer indexDiff = getIndexDiff();
138: StrBuffer grantDiff = getGrantDiff();
139:
140: boolean grantDifferent = grantDiff != null
141: && grantDiff.length() > 0;
142:
143: boolean indexDifferent = indexDiff != null
144: && indexDiff.length() > 0;
145:
146: if (colDiff.length() == 0 && !rename
147: && colsToBeAdded.size() == 0
148: && colsToBeRemoved.size() == 0 && refPk.equals(tPk)
149: && constraintsEqual && !indexDifferent
150: && !grantDifferent) {
151: return result;
152: }
153:
154: writer.appendOpenTag(result, this .indent, TAG_MODIFY_TABLE,
155: "name", target.getTableName());
156: result.append('\n');
157: if (rename) {
158: writer.appendOpenTag(result, myindent, TAG_RENAME_TABLE);
159: result.append('\n');
160: myindent.append(" ");
161: writer.appendTag(result, myindent,
162: ReportTable.TAG_TABLE_NAME, this .referenceTable
163: .getTable().getTableName());
164: myindent.removeFromEnd(2);
165: writer.appendCloseTag(result, myindent, TAG_RENAME_TABLE);
166: }
167:
168: appendAddColumns(result, colsToBeAdded);
169: appendRemoveColumns(result, colsToBeRemoved);
170:
171: String pkTagToUse = null;
172: String attr[] = new String[] { "name" };
173: String value[] = new String[1];
174: List pkcols = null;
175:
176: if (refPk.size() == 0 && tPk.size() > 0) {
177: value[0] = this .targetTable.getPrimaryKeyName();
178: pkTagToUse = TAG_REMOVE_PK;
179: pkcols = this .targetTable.getPrimaryKeyColumns();
180: } else if (refPk.size() > 0 && tPk.size() == 0) {
181: value[0] = this .referenceTable.getPrimaryKeyName();
182: pkTagToUse = TAG_ADD_PK;
183: pkcols = this .referenceTable.getPrimaryKeyColumns();
184: } else if (!refPk.equals(tPk)) {
185: value[0] = this .targetTable.getPrimaryKeyName();
186: pkTagToUse = TAG_MODIFY_PK;
187: pkcols = this .referenceTable.getPrimaryKeyColumns();
188: }
189:
190: if (pkcols != null) {
191: writer.appendOpenTag(result, myindent, pkTagToUse, attr,
192: value);
193: result.append('\n');
194: myindent.append(" ");
195: Iterator itr = pkcols.iterator();
196: while (itr.hasNext()) {
197: writer.appendTag(result, myindent,
198: ReportColumn.TAG_COLUMN_NAME, (String) itr
199: .next());
200: }
201: myindent.removeFromEnd(2);
202: writer.appendCloseTag(result, myindent, pkTagToUse);
203: }
204:
205: if (colDiff.length() > 0) {
206: result.append(colDiff);
207: }
208:
209: if (!constraintsEqual) {
210: writer.appendTag(result, myindent, TAG_TABLE_CONS,
211: this .referenceTable.getTableConstraints(), true);
212: }
213:
214: if (indexDifferent) {
215: result.append(indexDiff);
216: }
217: if (grantDifferent) {
218: result.append(grantDiff);
219: }
220: writer.appendCloseTag(result, this .indent, TAG_MODIFY_TABLE);
221: return result;
222: }
223:
224: private void appendAddColumns(StrBuffer result, List colsToAdd) {
225: Iterator itr = colsToAdd.iterator();
226: if (!itr.hasNext())
227: return;
228: StrBuffer myindent = new StrBuffer(this .indent);
229: myindent.append(" ");
230: writer.appendOpenTag(result, myindent, TAG_ADD_COLUMN);
231: result.append('\n');
232: myindent.append(" ");
233: while (itr.hasNext()) {
234: ReportColumn col = (ReportColumn) itr.next();
235: col.setNamespace(this .writer.getNamespace());
236: col.appendXml(result, myindent, false);
237: }
238: myindent.removeFromEnd(2);
239: writer.appendCloseTag(result, myindent, TAG_ADD_COLUMN);
240: result.append('\n');
241: }
242:
243: private void appendRemoveColumns(StrBuffer result, List colsToRemove) {
244: Iterator itr = colsToRemove.iterator();
245: if (!itr.hasNext())
246: return;
247: StrBuffer myindent = new StrBuffer(this .indent);
248: myindent.append(indent);
249: while (itr.hasNext()) {
250: ReportColumn col = (ReportColumn) itr.next();
251: writer.appendEmptyTag(result, myindent, TAG_REMOVE_COLUMN,
252: "name", col.getColumn().getColumnName());
253: result.append('\n');
254: }
255: }
256:
257: private StrBuffer getGrantDiff() {
258: if (!this .diff.getIncludeTableGrants())
259: return null;
260: ReportTableGrants reference = this .referenceTable.getGrants();
261: ReportTableGrants target = this .targetTable.getGrants();
262: if (reference == null && target == null)
263: return null;
264:
265: TableGrantDiff td = new TableGrantDiff(reference, target);
266: StrBuffer diffXml = td.getMigrateTargetXml(writer, indent);
267: return diffXml;
268: }
269:
270: private StrBuffer getIndexDiff() {
271: if (!this .diff.getIncludeIndex())
272: return null;
273:
274: Collection<IndexDefinition> ref = this .referenceTable
275: .getIndexList();
276: Collection<IndexDefinition> targ = this .targetTable
277: .getIndexList();
278: if (ref == null && targ == null)
279: return null;
280: IndexDiff id = new IndexDiff(ref, targ);
281: id.setTagWriter(this .writer);
282: id.setIndent(indent);
283: StrBuffer diffXml = id.getMigrateTargetXml();
284: return diffXml;
285: }
286:
287: /**
288: * Set the {@link workbench.db.report.TagWriter} to
289: * be used for writing the XML tags
290: */
291: public void setTagWriter(TagWriter tagWriter) {
292: this .writer = tagWriter;
293: }
294:
295: /**
296: * Set an indent for generating the XML
297: */
298: public void setIndent(String ind) {
299: if (ind == null)
300: this .indent = null;
301: this .indent = new StrBuffer(ind);
302: }
303:
304: /**
305: * Set an indent for generating the XML
306: */
307: public void setIndent(StrBuffer ind) {
308: this.indent = ind;
309: }
310:
311: }
|