001package io.ebeaninternal.dbmigration.model;
002
003import io.ebean.annotation.ConstraintMode;
004import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
005import io.ebeaninternal.dbmigration.migration.AlterColumn;
006import io.ebeaninternal.dbmigration.migration.Column;
007import io.ebeaninternal.dbmigration.migration.DdlScript;
008import io.ebeaninternal.server.deploy.DbMigrationInfo;
009
010import java.util.List;
011import java.util.Objects;
012
013/**
014 * A column in the logical model.
015 */
016public class MColumn {
017
018  private String name;
019  private String type;
020  private String checkConstraint;
021  private String checkConstraintName;
022  private String defaultValue;
023  private String references;
024  private String foreignKeyName;
025  private String foreignKeyIndex;
026  private ConstraintMode fkeyOnDelete;
027  private ConstraintMode fkeyOnUpdate;
028  private String comment;
029
030  private boolean historyExclude;
031  private boolean notnull;
032  private boolean primaryKey;
033  private boolean identity;
034
035  private String unique;
036
037  /**
038   * Special unique for OneToOne as we need to handle that different
039   * specifically for MsSqlServer.
040   */
041  private String uniqueOneToOne;
042
043  /**
044   * Temporary variable used when building the alter column changes.
045   */
046  private AlterColumn alterColumn;
047
048  private boolean draftOnly;
049
050  private List<DbMigrationInfo> dbMigrationInfos;
051
052  public MColumn(Column column) {
053    this.name = column.getName();
054    this.type = column.getType();
055    this.checkConstraint = column.getCheckConstraint();
056    this.checkConstraintName = column.getCheckConstraintName();
057    this.defaultValue = column.getDefaultValue();
058    this.comment = column.getComment();
059    this.references = column.getReferences();
060    this.foreignKeyName = column.getForeignKeyName();
061    this.foreignKeyIndex = column.getForeignKeyIndex();
062    this.fkeyOnDelete = fkeyMode(column.getForeignKeyOnDelete());
063    this.fkeyOnUpdate = fkeyMode(column.getForeignKeyOnUpdate());
064    this.notnull = Boolean.TRUE.equals(column.isNotnull());
065    this.primaryKey = Boolean.TRUE.equals(column.isPrimaryKey());
066    this.identity = Boolean.TRUE.equals(column.isIdentity());
067    this.unique = column.getUnique();
068    this.uniqueOneToOne = column.getUniqueOneToOne();
069    this.historyExclude = Boolean.TRUE.equals(column.isHistoryExclude());
070  }
071
072  private ConstraintMode fkeyMode(String mode) {
073    return (mode == null) ? null : ConstraintMode.valueOf(mode);
074  }
075
076  public MColumn(String name, String type) {
077    this.name = name;
078    this.type = type;
079  }
080
081  public MColumn(String name, String type, boolean notnull) {
082    this.name = name;
083    this.type = type;
084    this.notnull = notnull;
085  }
086
087  /**
088   * Return a copy of this column used for creating the associated draft table.
089   */
090  public MColumn copyForDraft() {
091    MColumn copy = new MColumn(name, type);
092    copy.draftOnly = draftOnly;
093    copy.checkConstraint = checkConstraint;
094    copy.checkConstraintName = checkConstraintName;
095    copy.defaultValue = defaultValue;
096    copy.dbMigrationInfos = dbMigrationInfos;
097    copy.references = references;
098    copy.comment = comment;
099    copy.foreignKeyName = foreignKeyName;
100    copy.foreignKeyIndex = foreignKeyIndex;
101    copy.fkeyOnUpdate = fkeyOnUpdate;
102    copy.fkeyOnDelete = fkeyOnDelete;
103    copy.historyExclude = historyExclude;
104    copy.notnull = notnull;
105    copy.primaryKey = primaryKey;
106    copy.identity = identity;
107    copy.unique = unique;
108    copy.uniqueOneToOne = uniqueOneToOne;
109    return copy;
110  }
111
112  public String getName() {
113    return name;
114  }
115
116  public String getType() {
117    return type;
118  }
119
120  public boolean isPrimaryKey() {
121    return primaryKey;
122  }
123
124  public void setPrimaryKey(boolean primaryKey) {
125    this.primaryKey = primaryKey;
126  }
127
128  public boolean isIdentity() {
129    return identity;
130  }
131
132  public void setIdentity(boolean identity) {
133    this.identity = identity;
134  }
135
136  public String getCheckConstraint() {
137    return checkConstraint;
138  }
139
140  public void setCheckConstraint(String checkConstraint) {
141    this.checkConstraint = checkConstraint;
142  }
143
144  public String getCheckConstraintName() {
145    return checkConstraintName;
146  }
147
148  public void setCheckConstraintName(String checkConstraintName) {
149    this.checkConstraintName = checkConstraintName;
150  }
151
152  public String getForeignKeyName() {
153    return foreignKeyName;
154  }
155
156  public void setForeignKeyName(String foreignKeyName) {
157    this.foreignKeyName = foreignKeyName;
158  }
159
160  public String getForeignKeyIndex() {
161    return foreignKeyIndex;
162  }
163
164  public void setForeignKeyIndex(String foreignKeyIndex) {
165    this.foreignKeyIndex = foreignKeyIndex;
166  }
167
168  public void setForeignKeyModes(ConstraintMode onDelete, ConstraintMode onUpdate) {
169    this.fkeyOnDelete = onDelete;
170    this.fkeyOnUpdate = onUpdate;
171  }
172
173  public String getDefaultValue() {
174    return defaultValue;
175  }
176
177  public void setDefaultValue(String defaultValue) {
178    this.defaultValue = defaultValue;
179  }
180
181  public String getReferences() {
182    return references;
183  }
184
185  public void setReferences(String references) {
186    this.references = references;
187  }
188
189  public boolean isNotnull() {
190    return notnull;
191  }
192
193  public void setNotnull(boolean notnull) {
194    this.notnull = notnull;
195  }
196
197  public boolean isHistoryExclude() {
198    return historyExclude;
199  }
200
201  public void setHistoryExclude(boolean historyExclude) {
202    this.historyExclude = historyExclude;
203  }
204
205  public void setUnique(String unique) {
206    this.unique = unique;
207  }
208
209  public String getUnique() {
210    return unique;
211  }
212
213  /**
214   * Set unique specifically for OneToOne mapping.
215   * We need special DDL for this case for SqlServer.
216   */
217  public void setUniqueOneToOne(String uniqueOneToOne) {
218    this.uniqueOneToOne = uniqueOneToOne;
219  }
220
221  /**
222   * Return true if this is unique for a OneToOne.
223   */
224  public String getUniqueOneToOne() {
225    return uniqueOneToOne;
226  }
227
228  /**
229   * Return the column comment.
230   */
231  public String getComment() {
232    return comment;
233  }
234
235  /**
236   * Set the column comment.
237   */
238  public void setComment(String comment) {
239    this.comment = comment;
240  }
241
242  /**
243   * Set the draftOnly status for this column.
244   */
245  public void setDraftOnly(boolean draftOnly) {
246    this.draftOnly = draftOnly;
247  }
248
249  /**
250   * Return the draftOnly status for this column.
251   */
252  public boolean isDraftOnly() {
253    return draftOnly;
254  }
255
256  /**
257   * Return true if this column should be included in History DB triggers etc.
258   */
259  public boolean isIncludeInHistory() {
260    return !draftOnly && !historyExclude;
261  }
262
263  public void clearForeignKey() {
264    this.references = null;
265    this.foreignKeyName = null;
266    this.fkeyOnDelete = null;
267    this.fkeyOnUpdate = null;
268  }
269
270  public Column createColumn() {
271
272    Column c = new Column();
273    c.setName(name);
274    c.setType(type);
275
276    if (notnull) c.setNotnull(true);
277    if (primaryKey) c.setPrimaryKey(true);
278    if (identity) c.setIdentity(true);
279    if (historyExclude) c.setHistoryExclude(true);
280
281    c.setCheckConstraint(checkConstraint);
282    c.setCheckConstraintName(checkConstraintName);
283    c.setReferences(references);
284    c.setForeignKeyName(foreignKeyName);
285    c.setForeignKeyIndex(foreignKeyIndex);
286    c.setForeignKeyOnDelete(fkeyModeOf(fkeyOnDelete));
287    c.setForeignKeyOnUpdate(fkeyModeOf(fkeyOnUpdate));
288    c.setDefaultValue(defaultValue);
289    c.setComment(comment);
290    c.setUnique(unique);
291    c.setUniqueOneToOne(uniqueOneToOne);
292
293    if (dbMigrationInfos != null) {
294      for (DbMigrationInfo info : dbMigrationInfos) {
295        if (!info.getPreAdd().isEmpty()) {
296          DdlScript script = new DdlScript();
297          script.getDdl().addAll(info.getPreAdd());
298          script.setPlatforms(info.joinPlatforms());
299          c.getBefore().add(script);
300        }
301
302        if (!info.getPostAdd().isEmpty()) {
303          DdlScript script = new DdlScript();
304          script.getDdl().addAll(info.getPostAdd());
305          script.setPlatforms(info.joinPlatforms());
306          c.getAfter().add(script);
307        }
308      }
309    }
310
311    return c;
312  }
313
314  private String fkeyModeOf(ConstraintMode mode) {
315    return (mode == null) ? null : mode.name();
316  }
317
318  protected static boolean different(String val1, String val2) {
319    return !Objects.equals(val1, val2);
320  }
321
322  private boolean hasValue(String val) {
323    return val != null && !val.isEmpty();
324  }
325
326  private boolean hasValue(Boolean val) {
327    return val != null;
328  }
329
330  private AlterColumn getAlterColumn(String tableName, boolean tableWithHistory) {
331    if (alterColumn == null) {
332      alterColumn = new AlterColumn();
333      alterColumn.setColumnName(name);
334      alterColumn.setTableName(tableName);
335      if (tableWithHistory) {
336        alterColumn.setWithHistory(Boolean.TRUE);
337      }
338
339      if (dbMigrationInfos != null) {
340        for (DbMigrationInfo info : dbMigrationInfos) {
341          if (!info.getPreAlter().isEmpty()) {
342            DdlScript script = new DdlScript();
343            script.getDdl().addAll(info.getPreAlter());
344            script.setPlatforms(info.joinPlatforms());
345            alterColumn.getBefore().add(script);
346          }
347
348          if (!info.getPostAlter().isEmpty()) {
349            DdlScript script = new DdlScript();
350            script.getDdl().addAll(info.getPostAlter());
351            script.setPlatforms(info.joinPlatforms());
352            alterColumn.getAfter().add(script);
353          }
354        }
355      }
356    }
357    return alterColumn;
358  }
359
360  /**
361   * Compare the column meta data and return true if there is a change that means
362   * the history table column needs
363   */
364  public void compare(ModelDiff modelDiff, MTable table, MColumn newColumn) {
365
366    this.dbMigrationInfos = newColumn.dbMigrationInfos;
367
368    boolean tableWithHistory = table.isWithHistory();
369    String tableName = table.getName();
370
371    // set to null and check at the end
372    this.alterColumn = null;
373
374    boolean changeBaseAttribute = false;
375
376    if (historyExclude != newColumn.historyExclude) {
377      getAlterColumn(tableName, tableWithHistory).setHistoryExclude(newColumn.historyExclude);
378    }
379
380    if (different(type, newColumn.type)) {
381      changeBaseAttribute = true;
382      getAlterColumn(tableName, tableWithHistory).setType(newColumn.type);
383    }
384    if (notnull != newColumn.notnull) {
385      changeBaseAttribute = true;
386      getAlterColumn(tableName, tableWithHistory).setNotnull(newColumn.notnull);
387    }
388    if (different(defaultValue, newColumn.defaultValue)) {
389      AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
390      if (newColumn.defaultValue == null) {
391        alter.setDefaultValue(DdlHelp.DROP_DEFAULT);
392      } else {
393        alter.setDefaultValue(newColumn.defaultValue);
394      }
395    }
396    if (different(comment, newColumn.comment)) {
397      AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
398      if (newColumn.comment == null) {
399        alter.setComment(DdlHelp.DROP_COMMENT);
400      } else {
401        alter.setComment(newColumn.comment);
402      }
403    }
404    if (different(checkConstraint, newColumn.checkConstraint)) {
405      AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
406      if (hasValue(checkConstraint) && !hasValue(newColumn.checkConstraint)) {
407        alter.setDropCheckConstraint(checkConstraintName);
408      }
409      if (hasValue(newColumn.checkConstraint)) {
410        alter.setCheckConstraintName(newColumn.checkConstraintName);
411        alter.setCheckConstraint(newColumn.checkConstraint);
412      }
413    }
414    if (different(references, newColumn.references)
415        || hasValue(newColumn.references) && fkeyOnDelete != newColumn.fkeyOnDelete
416        || hasValue(newColumn.references) && fkeyOnUpdate != newColumn.fkeyOnUpdate) {
417      // foreign key change
418      AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
419      if (hasValue(foreignKeyName)) {
420        alter.setDropForeignKey(foreignKeyName);
421      }
422      if (hasValue(foreignKeyIndex)) {
423        alter.setDropForeignKeyIndex(foreignKeyIndex);
424      }
425      if (hasValue(newColumn.references)) {
426        // add new foreign key constraint
427        alter.setReferences(newColumn.references);
428        alter.setForeignKeyName(newColumn.foreignKeyName);
429        alter.setForeignKeyIndex(newColumn.foreignKeyIndex);
430        if (newColumn.fkeyOnDelete != null) {
431          alter.setForeignKeyOnDelete(fkeyModeOf(newColumn.fkeyOnDelete));
432        }
433        if (newColumn.fkeyOnUpdate != null) {
434          alter.setForeignKeyOnUpdate(fkeyModeOf(newColumn.fkeyOnUpdate));
435        }
436      }
437    }
438
439    if (different(unique, newColumn.unique)) {
440      AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
441      if (hasValue(unique)) {
442        alter.setDropUnique(unique);
443      }
444      if (hasValue(newColumn.unique)) {
445        alter.setUnique(newColumn.unique);
446      }
447    }
448    if (different(uniqueOneToOne, newColumn.uniqueOneToOne)) {
449      AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
450      if (hasValue(uniqueOneToOne)) {
451        alter.setDropUnique(uniqueOneToOne);
452      }
453      if (hasValue(newColumn.uniqueOneToOne)) {
454        alter.setUniqueOneToOne(newColumn.uniqueOneToOne);
455      }
456    }
457
458    if (alterColumn != null) {
459      modelDiff.addAlterColumn(alterColumn);
460      if (changeBaseAttribute) {
461        // support reverting these changes
462        alterColumn.setCurrentType(type);
463        alterColumn.setCurrentNotnull(notnull);
464      }
465    }
466  }
467
468  public void setDbMigrationInfos(List<DbMigrationInfo> dbMigrationInfos) {
469    this.dbMigrationInfos = dbMigrationInfos;
470  }
471
472  /**
473   * Rename the column.
474   */
475  public MColumn rename(String newName) {
476    this.name = newName;
477    return this;
478  }
479
480  /**
481   * Apply changes based on the AlterColumn request.
482   */
483  public void apply(AlterColumn alterColumn) {
484
485    if (hasValue(alterColumn.getDropCheckConstraint())) {
486      checkConstraint = null;
487    }
488    if (hasValue(alterColumn.getDropForeignKey())) {
489      foreignKeyName = null;
490      references = null;
491    }
492    if (hasValue(alterColumn.getDropForeignKeyIndex())) {
493      foreignKeyIndex = null;
494    }
495    if (hasValue(alterColumn.getDropUnique())) {
496      unique = null;
497      uniqueOneToOne = null;
498    }
499
500    if (hasValue(alterColumn.getType())) {
501      type = alterColumn.getType();
502    }
503    if (hasValue(alterColumn.isNotnull())) {
504      notnull = alterColumn.isNotnull();
505    }
506    if (hasValue(alterColumn.getDefaultValue())) {
507      defaultValue = alterColumn.getDefaultValue();
508      if (DdlHelp.isDropDefault(defaultValue)) {
509        defaultValue = null;
510      }
511    }
512    if (hasValue(alterColumn.getCheckConstraint())) {
513      checkConstraint = alterColumn.getCheckConstraint();
514    }
515    if (hasValue(alterColumn.getCheckConstraintName())) {
516      checkConstraintName = alterColumn.getCheckConstraintName();
517    }
518    if (hasValue(alterColumn.getUnique())) {
519      unique = alterColumn.getUnique();
520    }
521    if (hasValue(alterColumn.getUniqueOneToOne())) {
522      uniqueOneToOne = alterColumn.getUniqueOneToOne();
523    }
524    if (hasValue(alterColumn.getReferences())) {
525      references = alterColumn.getReferences();
526    }
527    if (hasValue(alterColumn.getForeignKeyName())) {
528      foreignKeyName = alterColumn.getForeignKeyName();
529    }
530    if (hasValue(alterColumn.getForeignKeyIndex())) {
531      foreignKeyIndex = alterColumn.getForeignKeyIndex();
532    }
533    if (hasValue(alterColumn.getComment())) {
534      comment = alterColumn.getComment();
535      if (DdlHelp.isDropComment(comment)) {
536        comment = null;
537      }
538    }
539    if (hasValue(alterColumn.getForeignKeyOnDelete())) {
540      fkeyOnDelete = fkeyMode(alterColumn.getForeignKeyOnDelete());
541    }
542    if (hasValue(alterColumn.getForeignKeyOnUpdate())) {
543      fkeyOnUpdate = fkeyMode(alterColumn.getForeignKeyOnUpdate());
544    }
545    if (hasValue(alterColumn.isHistoryExclude())) {
546      historyExclude = alterColumn.isHistoryExclude();
547    }
548  }
549}