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