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 @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 * @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}