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}