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}