001package io.ebeaninternal.dbmigration.model.build;
002
003import io.ebean.config.DbConstraintNaming;
004import io.ebean.config.dbplatform.DatabasePlatform;
005import io.ebean.config.dbplatform.DbPlatformType;
006import io.ebean.config.dbplatform.DbPlatformTypeMapping;
007import io.ebean.core.type.ScalarType;
008import io.ebeaninternal.dbmigration.ddlgeneration.platform.DefaultConstraintMaxLength;
009import io.ebeaninternal.dbmigration.model.MColumn;
010import io.ebeaninternal.dbmigration.model.MCompoundForeignKey;
011import io.ebeaninternal.dbmigration.model.MIndex;
012import io.ebeaninternal.dbmigration.model.MTable;
013import io.ebeaninternal.dbmigration.model.ModelContainer;
014import io.ebeaninternal.server.deploy.BeanDescriptor;
015import io.ebeaninternal.server.deploy.BeanProperty;
016import io.ebeaninternal.server.deploy.TableJoin;
017import io.ebeaninternal.server.deploy.TableJoinColumn;
018
019import java.util.Collection;
020import java.util.List;
021import java.util.concurrent.atomic.AtomicInteger;
022
023/**
024 * The context used during DDL generation.
025 */
026public class ModelBuildContext {
027
028  /**
029   * Use platform agnostic logical types. These types are converted to
030   * platform specific types in the DDL generation.
031   */
032  private final DbPlatformTypeMapping dbTypeMap = DbPlatformTypeMapping.logicalTypes();
033
034  private final ModelContainer model;
035
036  private final DatabasePlatform databasePlatform;
037
038  private final DbConstraintNaming constraintNaming;
039
040  private final DbConstraintNaming.MaxLength maxLength;
041
042  private final boolean platformTypes;
043
044  public ModelBuildContext(ModelContainer model, DatabasePlatform databasePlatform, DbConstraintNaming naming, boolean platformTypes) {
045    this.model = model;
046    this.databasePlatform = databasePlatform;
047    this.constraintNaming = naming;
048    this.platformTypes = platformTypes;
049    this.maxLength = maxLength();
050  }
051
052  /**
053   * Create the max length handling for constraint names.
054   */
055  private DbConstraintNaming.MaxLength maxLength() {
056    if (constraintNaming.getMaxLength() != null) {
057      return constraintNaming.getMaxLength();
058    }
059    return new DefaultConstraintMaxLength(databasePlatform.getMaxConstraintNameLength());
060  }
061
062  /**
063   * Adjust the foreign key references on any draft tables (that reference other draft tables).
064   * This is called as a 'second pass' after all the draft tables have been identified.
065   */
066  public void adjustDraftReferences() {
067    model.adjustDraftReferences();
068  }
069
070  public String normaliseTable(String baseTable) {
071    return constraintNaming.normaliseTable(baseTable);
072  }
073
074  /**
075   * Take into account max length and quoted identifiers in constraint and index names.
076   */
077  private String name(String constraintName, int indexCount) {
078    return databasePlatform.convertQuotedIdentifiers(maxLength(constraintName, indexCount));
079  }
080
081  private String maxLength(String constraintName, int indexCount) {
082    return maxLength.maxLength(constraintName, indexCount);
083  }
084
085  public String primaryKeyName(String tableName) {
086    return name(constraintNaming.primaryKeyName(tableName), 0);
087  }
088
089  public String foreignKeyConstraintName(String tableName, String columnName, int foreignKeyCount) {
090    return name(constraintNaming.foreignKeyConstraintName(tableName, columnName), foreignKeyCount);
091  }
092
093  public String foreignKeyIndexName(String tableName, String[] columns, int indexCount) {
094    return name(constraintNaming.foreignKeyIndexName(tableName, columns), indexCount);
095  }
096
097  public String foreignKeyIndexName(String tableName, String column, int indexCount) {
098    return name(constraintNaming.foreignKeyIndexName(tableName, column), indexCount);
099  }
100
101  public String indexName(String tableName, String column, int indexCount) {
102    return name(constraintNaming.indexName(tableName, column), indexCount);
103  }
104
105  public String indexName(String tableName, String[] columns, int indexCount) {
106    return name(constraintNaming.indexName(tableName, columns), indexCount);
107  }
108
109  public String uniqueConstraintName(String tableName, String columnName, int indexCount) {
110    return name(constraintNaming.uniqueConstraintName(tableName, columnName), indexCount);
111  }
112
113  public String uniqueConstraintName(String tableName, String[] columnNames, int indexCount) {
114    return name(constraintNaming.uniqueConstraintName(tableName, columnNames), indexCount);
115  }
116
117  public String checkConstraintName(String tableName, String columnName, int checkCount) {
118    return name(constraintNaming.checkConstraintName(tableName, columnName), checkCount);
119  }
120
121  public MTable addTable(MTable table) {
122    return model.addTable(table);
123  }
124
125  public void addTableElementCollection(MTable table) {
126    model.addTableElementCollection(table);
127  }
128
129  public void addIndex(MIndex index) {
130    model.addIndex(index);
131  }
132
133  /**
134   * Return the map used to determine the DB specific type
135   * for a given bean property.
136   */
137  public DbPlatformTypeMapping getDbTypeMap() {
138    return dbTypeMap;
139  }
140
141
142  /**
143   * Render the DB type for this property given the strict mode.
144   */
145  public String getColumnDefn(BeanProperty p, boolean strict) {
146    DbPlatformType dbType = getDbType(p);
147    if (dbType == null) {
148      throw new IllegalStateException("Unknown DbType mapping for " + p.getFullBeanName());
149    }
150    return p.renderDbType(dbType, strict);
151  }
152
153  private DbPlatformType getDbType(BeanProperty p) {
154
155    if (p.isDbEncrypted()) {
156      return dbTypeMap.get(p.getDbEncryptedType());
157    }
158    if (p.isLocalEncrypted()) {
159      // scalar type potentially wrapping varbinary db type
160      ScalarType<Object> scalarType = p.getScalarType();
161      int jdbcType = scalarType.getJdbcType();
162      return dbTypeMap.get(jdbcType);
163    }
164
165    // can be the logical JSON types (JSON, JSONB, JSONClob, JSONBlob, JSONVarchar)
166    int dbType = p.getDbType(platformTypes);
167    if (dbType == 0) {
168      throw new RuntimeException("No scalarType defined for " + p.getFullBeanName());
169    }
170    return dbTypeMap.get(dbType);
171  }
172
173  /**
174   * Create the draft table for a given table.
175   */
176  public void createDraft(MTable table, boolean draftable) {
177
178    MTable draftTable = table.createDraftTable();
179    draftTable.setPkName(primaryKeyName(draftTable.getName()));
180
181    if (draftable) {
182      // Add a FK from @Draftable live table back to it's draft table)
183      List<MColumn> pkCols = table.primaryKeyColumns();
184      if (pkCols.size() == 1) {
185        // only doing this for single column PK at this stage
186        MColumn pk = pkCols.get(0);
187        pk.setReferences(draftTable.getName() + "." + pk.getName());
188        pk.setForeignKeyName(foreignKeyConstraintName(table.getName(), pk.getName(), 0));
189      }
190    }
191
192    int fkCount = 0;
193    int ixCount = 0;
194    int uqCount = 0;
195    Collection<MColumn> cols = draftTable.allColumns();
196    for (MColumn col : cols) {
197      if (col.getForeignKeyName() != null) {
198        // Note that we adjust the 'references' table later in a second pass
199        // after we know all the tables that are 'draftable'
200        //col.setReferences(refTable + "." + refColumn);
201        col.setForeignKeyName(foreignKeyConstraintName(draftTable.getName(), col.getName(), ++fkCount));
202
203        String[] indexCols = {col.getName()};
204        col.setForeignKeyIndex(foreignKeyIndexName(draftTable.getName(), indexCols, ++ixCount));
205      }
206      // adjust the unique constraint names
207      if (col.getUnique() != null) {
208        col.setUnique(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
209      }
210      if (col.getUniqueOneToOne() != null) {
211        col.setUniqueOneToOne(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
212      }
213    }
214
215    addTable(draftTable);
216  }
217
218  /**
219   * Return a builder to add foreign keys.
220   */
221  public FkeyBuilder fkeyBuilder(MTable destTable) {
222    return new FkeyBuilder(this, destTable);
223  }
224
225  public static class FkeyBuilder {
226
227    private final AtomicInteger count = new AtomicInteger();
228
229    private final ModelBuildContext ctx;
230
231    private final MTable destTable;
232
233    private final String tableName;
234
235    FkeyBuilder(ModelBuildContext ctx, MTable destTable) {
236      this.ctx = ctx;
237      this.destTable = destTable;
238      this.tableName = destTable.getName();
239    }
240
241    /**
242     * Add a foreign key based on the table join.
243     */
244    public FkeyBuilder addForeignKey(BeanDescriptor<?> desc, TableJoin tableJoin, boolean direction) {
245
246      String baseTable = ctx.normaliseTable(desc.getBaseTable());
247      String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, count.incrementAndGet());
248      String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, count.get());
249
250      MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex);
251
252      for (TableJoinColumn column : tableJoin.columns()) {
253        String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
254        String refCol = !direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
255        foreignKey.addColumnPair(localCol, refCol);
256      }
257
258      destTable.addForeignKey(foreignKey);
259      return this;
260    }
261
262  }
263}