001package io.ebeaninternal.dbmigration.ddlgeneration;
002
003import java.io.IOException;
004import java.util.Map;
005import java.util.TreeMap;
006import java.util.function.Function;
007
008import io.ebeaninternal.dbmigration.ddlgeneration.platform.BaseDdlBuffer;
009import io.ebeaninternal.dbmigration.model.MConfiguration;
010import io.ebeaninternal.dbmigration.model.MTable;
011import io.ebeaninternal.dbmigration.model.ModelContainer;
012
013/**
014 * Write context holding the buffers for both apply and rollback DDL. Description of the apply buffers:
015 * <ul>
016 * <li><b>applyDropDependencies:</b> Contains drops for foreign keys, indices, constraint or drop history table</li>
017 * <li><b>apply:</b> Contains &#64;DbMigraion.before, create table, create sequence or disable system versioning statements</li>
018 * <li><b>applyAlterTables:</b> Contains table alters (only that change the table data structure, there may be table alters like
019 * constraints etc. in postAlter)</li>
020 * <li><b>applyPostAlter:</b> Contains check constraints, unique constraints (which CAN be an index), column and table comments,
021 * &#64;DbMigraion.after, drop tables, drop sequences or enable system versioning statement</li>
022 * <li><b>applyForeignKeys: Contains foreign keys and indices.</b>
023 * <li><b>applyHistoryView:</b> The views for trigger based history support</li>
024 * <li><b>applyHistoryTrigger:</b> The triggers for trigger based history support</li>
025 * </ul>
026 */
027public class DdlWrite {
028
029  private final ModelContainer currentModel;
030
031  private final DdlBuffer applyDropDependencies = new BaseDdlBuffer();
032
033  private final DdlBuffer apply = new BaseDdlBuffer();
034
035  private final Map<String, DdlAlterTable> applyAlterTables = new TreeMap<>();
036
037  private final DdlBuffer applyPostAlter = new BaseDdlBuffer();
038
039  private final DdlBuffer applyForeignKeys = new BaseDdlBuffer();
040
041  private final DdlBuffer dropAllForeignKeys = new BaseDdlBuffer();
042
043  private final DdlBuffer dropAll = new BaseDdlBuffer();
044
045  private final DdlOptions options;
046
047  /**
048   * Create without any configuration or current model (no history support).
049   */
050  public DdlWrite() {
051    this(new MConfiguration(), new ModelContainer(), new DdlOptions());
052  }
053
054  /**
055   * Create with a configuration.
056   */
057  public DdlWrite(MConfiguration configuration, ModelContainer currentModel, DdlOptions options) {
058    this.currentModel = currentModel;
059    this.options = options;
060  }
061
062  /**
063   * Return the DDL options.
064   */
065  public DdlOptions getOptions() {
066    return options;
067  }
068
069  /**
070   * Return the Table information from the current model.
071   * <p>
072   * This is typically required for the history support (used to determine the list of columns
073   * included in the history when creating or recreating the associated trigger/stored procedure).
074   * </p>
075   */
076  public MTable getTable(String tableName) {
077    return currentModel.getTable(tableName);
078  }
079
080  /**
081   * Return true if the apply buffers are all empty.
082   */
083  public boolean isApplyEmpty() {
084    return apply.getBuffer().isEmpty()
085      && applyAlterTables.isEmpty()
086      && applyPostAlter.getBuffer().isEmpty()
087      && applyForeignKeys.getBuffer().isEmpty()
088      && applyDropDependencies.getBuffer().isEmpty();
089  }
090
091  /**
092   * Return the buffer that POST ALTER is written to.
093   */
094  public DdlBuffer applyPostAlter() {
095    return applyPostAlter;
096  }
097
098  /**
099   * Return the buffer that APPLY DDL is written to.
100   */
101  public DdlBuffer apply() {
102    return apply;
103  }
104
105  /**
106   * Return the buffer that executes early to drop dependencies like views etc.
107   */
108  public DdlBuffer applyDropDependencies() {
109    return applyDropDependencies;
110  }
111
112  /**
113   * Creates or returns the DdlAlterTable statement for <code>tablename</code>.
114   * 
115   * Note: All alters on a particular table are sorted in natural order in the ddl script. This allows optimizing the alters by
116   * merging them or doing a reorg table after altering is done.
117   * 
118   * @param tableName the table name
119   * @param factory   the factory to construct a new object
120   * @return
121   */
122  public DdlAlterTable applyAlterTable(String tableName, Function<String, DdlAlterTable> factory) {
123    return applyAlterTables.computeIfAbsent(tableName, factory);
124  }
125
126  /**
127   * Return the buffer that APPLY DDL is written to for foreign keys and their associated indexes.
128   * <p>
129   * Statements added to this buffer are executed after all the normal apply statements and
130   * typically 'add foreign key' is added to this buffer.
131   */
132  public DdlBuffer applyForeignKeys() {
133    return applyForeignKeys;
134  }
135
136  /**
137   * Return the buffer used for the 'drop all DDL' for dropping foreign keys and associated indexes.
138   */
139  public DdlBuffer dropAllForeignKeys() {
140    return dropAllForeignKeys;
141  }
142
143  /**
144   * Return the buffer used for the 'drop all DDL' to drop tables, views and history triggers etc.
145   */
146  public DdlBuffer dropAll() {
147    return dropAll;
148  }
149
150  /**
151   * Writes the apply ddl to the target.
152   */
153  public void writeApply(Appendable target) throws IOException {
154    if (!applyDropDependencies.isEmpty()) {
155      target.append("-- drop dependencies\n");
156      target.append(applyDropDependencies.getBuffer());
157    }
158    if (!apply.isEmpty()) {
159      target.append("-- apply changes\n");
160      target.append(apply.getBuffer());
161    }
162    if (!applyAlterTables.isEmpty()) {
163      target.append("-- apply alter tables\n");
164      for (DdlAlterTable alterTable : applyAlterTables.values()) {
165        alterTable.write(target);
166      }
167    }
168    if (!applyPostAlter.isEmpty()) {
169      target.append("-- apply post alter\n");
170      target.append(applyPostAlter.getBuffer());
171    }
172    if (!applyForeignKeys.isEmpty()) {
173      target.append("-- foreign keys and indices\n");
174      target.append(applyForeignKeys.getBuffer());
175    }
176  }
177
178  /**
179   * Writes the drop all ddl to the target.
180   */
181  public void writeDropAll(Appendable target) throws IOException {
182    if (!dropAllForeignKeys.isEmpty()) {
183      target.append("-- drop all foreign keys\n");
184      target.append(dropAllForeignKeys.getBuffer());
185    }
186    if (!dropAll.isEmpty()) {
187      target.append("-- drop all\n");
188      target.append(dropAll.getBuffer());
189    }
190  }
191
192  /**
193   * Returns all create statements. Mainly used for unit-tests
194   */
195  @Override
196  public String toString() {
197    StringBuilder sb = new StringBuilder();
198    try {
199      writeDropAll(sb);
200      writeApply(sb);
201    } catch (IOException e) {
202      // can not happen
203    }
204    return sb.toString();
205  }
206
207}