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}