001package io.ebeaninternal.dbmigration.ddlgeneration.platform;
002
003import io.ebean.config.DatabaseConfig;
004import io.ebean.config.DbConstraintNaming;
005import io.ebeaninternal.dbmigration.ddlgeneration.DdlAlterTable;
006import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
007import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
008import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
009import io.ebeaninternal.dbmigration.migration.AlterColumn;
010import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
011import io.ebeaninternal.dbmigration.model.MTable;
012
013/**
014 * @author Vilmos Nagy
015 */
016public class SqlServerHistoryDdl implements PlatformHistoryDdl {
017
018  private String systemPeriodStart;
019  private String systemPeriodEnd;
020  private PlatformDdl platformDdl;
021  protected DbConstraintNaming constraintNaming;
022  protected String historySuffix;
023
024  @Override
025  public void configure(DatabaseConfig config, PlatformDdl platformDdl) {
026    this.systemPeriodStart = config.getAsOfSysPeriod() + "From";
027    this.systemPeriodEnd = config.getAsOfSysPeriod() + "To";
028    this.platformDdl = platformDdl;
029
030    this.constraintNaming = config.getConstraintNaming();
031    this.historySuffix = config.getHistoryTableSuffix();
032  }
033
034  @Override
035  public void createWithHistory(DdlWrite writer, MTable table) {
036    String baseTable = table.getName();
037    enableSystemVersioning(writer, baseTable);
038  }
039
040  private void enableSystemVersioning(DdlWrite writer, String baseTable) {
041    DdlBuffer apply = writer.applyPostAlter();
042    apply.append("alter table ").append(quote(baseTable)).newLine()
043      .append("    add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
044      .append("        ").append(systemPeriodEnd).append("   datetime2 GENERATED ALWAYS AS ROW END   NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
045      .append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
046
047    apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=")
048      .append(historyTableWithSchema(baseTable)).append("))").endOfStatement();
049
050    DdlBuffer drop = writer.dropAll();
051    drop.append("IF OBJECT_ID('").append(quote(baseTable)).append("', 'U') IS NOT NULL alter table ")
052      .append(quote(baseTable)).append(" set (system_versioning = off)").endOfStatement();
053    drop.append("IF OBJECT_ID('").append(historyTableName(baseTable)).append("', 'U') IS NOT NULL drop table ")
054      .append(historyTableName(baseTable)).endOfStatement();
055  }
056
057  @Override
058  public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) {
059    String baseTable = dropHistoryTable.getBaseTable();
060    // drop default constraints
061    AlterColumn alter = new AlterColumn();
062    alter.setTableName(baseTable);
063    alter.setDefaultValue(DdlHelp.DROP_DEFAULT);
064    alter.setColumnName(systemPeriodStart);
065    platformDdl.alterColumn(writer, alter);
066    alter.setColumnName(systemPeriodEnd);
067    platformDdl.alterColumn(writer, alter);
068
069    // switch of versioning & period - must be done before altering
070    DdlBuffer apply = writer.apply();
071    apply.append("-- dropping history support for ").append(baseTable).endOfStatement();
072    apply.append("alter table ").append(quote(baseTable)).append(" set (system_versioning = off)").endOfStatement();
073    apply.append("alter table ").append(quote(baseTable)).append(" drop period for system_time").endOfStatement();
074    apply.end();
075    // now drop tables & columns, they will go to alter table/post alter buffers
076    platformDdl.alterTableDropColumn(writer, baseTable, systemPeriodStart);
077    platformDdl.alterTableDropColumn(writer, baseTable, systemPeriodEnd);
078    writer.applyPostAlter().appendStatement(platformDdl.dropTable(historyTableName(baseTable)));
079  }
080
081  @Override
082  public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) {
083    String baseTable = addHistoryTable.getBaseTable();
084    enableSystemVersioning(writer, baseTable);
085  }
086
087  @Override
088  public void updateTriggers(DdlWrite writer, String tableName) {
089    DdlAlterTable alter = platformDdl.alterTable(writer, tableName);
090    writer.getTable(tableName);
091    if (!alter.isHistoryHandled()) {
092      // SQL Server 2016 does not need triggers
093      DdlBuffer apply = writer.apply();
094      apply.append("-- alter table ").append(quote(tableName)).append(" set (system_versioning = off (history_table=")
095        .append(historyTableWithSchema(tableName)).append("))").endOfStatement();
096      apply.append("-- history migration goes here").newLine();
097      apply.append("-- alter table ").append(quote(tableName)).append(" set (system_versioning = on (history_table=")
098        .append(historyTableWithSchema(tableName)).append("))").endOfStatement();
099    }
100    alter.setHistoryHandled();
101  }
102
103  protected String quote(String baseTable) {
104    return platformDdl.quote(baseTable);
105  }
106
107  protected String normalise(String tableName) {
108    return constraintNaming.normaliseTable(tableName);
109  }
110
111  protected String historyTableName(String baseTableName) {
112    return normalise(baseTableName) + historySuffix;
113  }
114
115  protected String historyTableWithSchema(String baseTableName) {
116    String historyTable = historyTableName(baseTableName);
117    int lastPeriod = baseTableName.lastIndexOf('.');
118    if (lastPeriod == -1) {
119      // history must contain schema, add the default schema if none was specified
120      return "dbo." + historyTable;
121    } else {
122      return baseTableName.substring(0, lastPeriod + 1) + historyTable;
123    }
124  }
125
126}