added migration generation

This commit is contained in:
wea_ondara
2022-08-14 12:41:44 +02:00
parent 23e046ef7e
commit b11ae150b3
85 changed files with 4206 additions and 121 deletions

View File

@@ -1,4 +1,6 @@
package jef.model;
public abstract class DbContext {
public void onModelCreate(ModelBuilder mb) {
}
}

View File

@@ -8,9 +8,12 @@ import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint;
import jef.model.constraints.PrimaryKeyConstraint;
import jef.model.constraints.UniqueConstraint;
import jef.model.constraints.UniqueKeyConstraint;
import jef.serializable.SerializableFunction;
import jef.serializable.SerializableObject;
import jef.util.Check;
import jef.util.Util;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@@ -18,33 +21,66 @@ import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@Getter
@Setter
public class DbEntity<T extends SerializableObject> {
private final Class<T> type;
private final String typeName;
@Setter(value = AccessLevel.PACKAGE)
private Class<T> type;
private final List<DbField<?>> fields;
private String name;
private PrimaryKeyConstraint primaryKey;
private final List<ForeignKeyConstraint> foreignKeys = new ArrayList<>();
private final List<UniqueConstraint> uniqueKeys = new ArrayList<>();
private final List<UniqueKeyConstraint> uniqueKeys = new ArrayList<>();
private final List<KeyConstraint> keys = new ArrayList<>();
private final List<IndexConstraint> indexes = new ArrayList<>();
DbEntity(Class<T> type) {
this(type, new ArrayList<>());
//only used for migrations
DbEntity(String typeName) {
this.type = (Class) Util.tryGet(() -> Class.forName(typeName)).orElse(null);
this.typeName = typeName;
this.fields = new ArrayList<>();
var split = typeName.split("\\.");
this.name = split[split.length - 1];
}
DbEntity(Class<T> type, List<DbField<?>> fields) {
this.type = type;
this.fields = fields;
this.name = type.getSimpleName();
DbEntity(Class<T> type) {
this(type, type.getSimpleName());
}
DbEntity(Class<T> type, String name) {
this(type, name, new ArrayList<>());
}
DbEntity(Class<T> type, String name, List<DbField<?>> fields) {
this.type = Check.notNull(type, "type");
this.typeName = type.getName();
this.fields = Check.notNull(fields, "fields");
this.name = Check.notNull(name, "name");
}
public List<DbField<?>> getFields() {
return Collections.unmodifiableList(fields);
}
public List<ForeignKeyConstraint> getForeignKeys() {
return Collections.unmodifiableList(foreignKeys);
}
public List<UniqueKeyConstraint> getUniqueKeys() {
return Collections.unmodifiableList(uniqueKeys);
}
public List<KeyConstraint> getKeys() {
return Collections.unmodifiableList(keys);
}
public List<IndexConstraint> getIndexes() {
return Collections.unmodifiableList(indexes);
}
/**
* Returns the database model for the requested property or null if not present.
*
@@ -62,6 +98,10 @@ public class DbEntity<T extends SerializableObject> {
return (DbField<R>) fields.stream().filter(e -> e.getField().equals(field)).findFirst().orElse(null);
}
public <R> DbField<R> getField(String name) {
return (DbField<R>) fields.stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null);
}
public <R> DbField<R> getOrCreateField(SerializableFunction<T, R> getter) {
try {
var prop = getField(getter);
@@ -93,6 +133,19 @@ public class DbEntity<T extends SerializableObject> {
}
}
public <R> DbField<R> getOrCreateField(String name, String typeName) {
try {
var prop = (DbField<R>) fields.stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null);
if (prop == null) {
prop = new DbField<>(this, name, typeName);
fields.add(prop);
}
return prop;
} catch (Exception e) {
throw new RuntimeException("Invalid expression", e);
}
}
public <R> DbField<R> addIfAbsent(DbField<R> field) {
try {
var prop = (DbField<R>) fields.stream().filter(e -> e.getName().equals(field.getName())).findFirst().orElse(null);
@@ -106,6 +159,26 @@ public class DbEntity<T extends SerializableObject> {
}
}
public boolean dropField(DbField<?> field) {
return this.fields.remove(field);
}
public boolean dropForeignKey(ForeignKeyConstraint foreignKey) {
return this.foreignKeys.remove(foreignKey);
}
public boolean dropUniqueKey(UniqueKeyConstraint uniqueKey) {
return this.uniqueKeys.remove(uniqueKey);
}
public boolean dropKey(KeyConstraint key) {
return this.keys.remove(key);
}
public boolean dropIndex(IndexConstraint index) {
return this.indexes.remove(index);
}
private <R> String extractFieldName(SerializableFunction<T, R> getter) {
try {
var expr = new AsmParser(getter).parse();
@@ -125,15 +198,9 @@ public class DbEntity<T extends SerializableObject> {
}
}
public void addUniqueContstraint(UniqueConstraint uniqueConstraint) {
if (!uniqueKeys.contains(uniqueConstraint)) {
uniqueKeys.add(uniqueConstraint);
}
}
public void addIndex(IndexConstraint index) {
if (!indexes.contains(index)) {
indexes.add(index);
public void addUniqueKey(UniqueKeyConstraint uniqueKeyConstraint) {
if (!uniqueKeys.contains(uniqueKeyConstraint)) {
uniqueKeys.add(uniqueKeyConstraint);
}
}
@@ -143,11 +210,37 @@ public class DbEntity<T extends SerializableObject> {
}
}
public void addIndex(IndexConstraint index) {
if (!indexes.contains(index)) {
indexes.add(index);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DbEntity<?> dbEntity = (DbEntity<?>) o;
return typeName.equals(dbEntity.typeName)
&& fields.equals(dbEntity.fields)
&& name.equals(dbEntity.name)
&& Objects.equals(primaryKey, dbEntity.primaryKey)
&& foreignKeys.equals(dbEntity.foreignKeys)
&& uniqueKeys.equals(dbEntity.uniqueKeys)
&& keys.equals(dbEntity.keys)
&& indexes.equals(dbEntity.indexes);
}
@Override
public int hashCode() {
return Objects.hash(typeName, type, fields, name, primaryKey, foreignKeys, uniqueKeys, keys, indexes);
}
@Override
public String toString() {
return "DbEntity{" +
"type=" + type +
", name='" + name + '\'' +
'}';
"type=" + type +
", name='" + name + '\'' +
'}';
}
}

View File

@@ -2,8 +2,10 @@ package jef.model;
import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint;
import jef.model.constraints.UniqueConstraint;
import jef.model.constraints.UniqueKeyConstraint;
import jef.serializable.SerializableObject;
import jef.util.Check;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@@ -11,28 +13,43 @@ import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@Getter
@Setter
public class DbField<T> {
private final DbEntity<? extends SerializableObject> entity;
private final Class<T> type;
private final Field field;
private final String typeName;
@Setter(value = AccessLevel.PACKAGE)
private Class<T> type;
@Setter(value = AccessLevel.PACKAGE)
private Field field;
private boolean isModelField;
private boolean isDatabaseField;
private DbField<?> foreignKeyModelLink;
private String name;
private boolean notNull = false;
DbField(DbEntity<? extends SerializableObject> entity, String name, String typeName) {
this.entity = Check.notNull(entity, "entity");
this.type = null;
this.typeName = Check.notNull(typeName, "typeName");
this.field = null;
this.name = Check.notNull(name, "name");
this.isModelField = false;
this.isDatabaseField = true;
}
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field) {
this(entity, type, field, field.getName());
}
DbField(DbEntity<? extends SerializableObject> entity, Class<T> type, Field field, String name) {
this.entity = entity;
this.type = type;
this.entity = Check.notNull(entity, "entity");
this.type = Check.notNull(type, "type");
this.typeName = type.getName();
this.field = field;
this.name = name;
this.name = Check.notNull(name, "name");
this.isModelField = field != null;
this.isDatabaseField = !Collection.class.isAssignableFrom(type) && !SerializableObject.class.isAssignableFrom(type);
}
@@ -48,9 +65,9 @@ public class DbField<T> {
public void setUnique(boolean unique) {
var constr = entity.getUniqueKeys().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == this).findFirst();
if (!constr.isPresent() && unique) {
entity.getUniqueKeys().add(new UniqueConstraint(entity, new ArrayList<>(List.of(this))));
entity.addUniqueKey(new UniqueKeyConstraint(entity, new ArrayList<>(List.of(this))));
} else if (constr.isPresent() && !unique) {
entity.getUniqueKeys().remove(constr.get());
entity.dropUniqueKey(constr.get());
} //else do nothing
}
@@ -61,9 +78,9 @@ public class DbField<T> {
public void setIndex(boolean indexed) {
var constr = entity.getIndexes().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == this).findFirst();
if (!constr.isPresent() && indexed) {
entity.getIndexes().add(new IndexConstraint(entity, new ArrayList<>(List.of(this))));
entity.addIndex(new IndexConstraint(entity, new ArrayList<>(List.of(this))));
} else if (constr.isPresent() && !indexed) {
entity.getIndexes().remove(constr.get());
entity.dropIndex(constr.get());
} //else do nothing
}
@@ -74,18 +91,68 @@ public class DbField<T> {
public void setKey(boolean keyed) {
var constr = entity.getKeys().stream().filter(u -> u.getFields().size() == 1 && u.getFields().get(0) == this).findFirst();
if (!constr.isPresent() && keyed) {
entity.getKeys().add(new KeyConstraint(entity, new ArrayList<>(List.of(this))));
entity.addKey(new KeyConstraint(entity, new ArrayList<>(List.of(this))));
} else if (constr.isPresent() && !keyed) {
entity.getKeys().remove(constr.get());
entity.dropKey(constr.get());
} //else do nothing
}
public DbField<T> setModelField(boolean modelField) {
isModelField = modelField;
return this;
}
public DbField<T> setDatabaseField(boolean databaseField) {
isDatabaseField = databaseField;
return this;
}
public DbField<T> setNotNull(boolean notNull) {
this.notNull = notNull;
return this;
}
@Override
public boolean equals(Object o) {
if (!equalsCommon(o)) {
return false;
}
DbField<?> dbField = (DbField<?>) o;
return name.equals(dbField.name);
// && Objects.equals(field == null ? null : field.getName(), dbField.field == null ? null : dbField.field.getName());
}
public boolean equalsExceptName(Object o) {
return equalsCommon(o);
}
private boolean equalsCommon(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DbField<?> dbField = (DbField<?>) o;
return isModelField == dbField.isModelField
&& isDatabaseField == dbField.isDatabaseField
&& notNull == dbField.notNull
&& entity.getName().equals(dbField.entity.getName())
&& typeName.equals(dbField.typeName)
// && Objects.equals(type, dbField.type)
&& Objects.equals(foreignKeyModelLink == null ? null : foreignKeyModelLink.getName(),
dbField.foreignKeyModelLink == null ? null : dbField.foreignKeyModelLink.getName());
}
@Override
public int hashCode() {
return Objects.hash(entity.getName(), typeName, type, field == null ? null : field.getName(), isModelField, isDatabaseField,
foreignKeyModelLink == null ? null : foreignKeyModelLink.getName(),
name, notNull);
}
@Override
public String toString() {
return "DbField{" +
"name=" + name +
", type=" + type.getSimpleName() + (notNull ? "" : "?") +
", entity=" + entity +
'}';
"name=" + name +
", typeName=" + typeName + (notNull ? "" : "?") +
", entity=" + entity +
'}';
}
}

View File

@@ -19,12 +19,13 @@ class EntityInitializer {
throw new ModelException("DbSet " + ctxfield.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation");
}
var dbsetClazz = (Class<? extends SerializableObject>) clazzAnnotation.clazz();
initEntity(mb, dbsetClazz);
initEntity(mb, dbsetClazz, ctxfield.getName());
}
}
static void initEntity(ModelBuilder mb, Class<? extends SerializableObject> clazz) {
static void initEntity(ModelBuilder mb, Class<? extends SerializableObject> clazz, String name) {
var entity = mb.getOrCreateEntity(clazz);
entity.setName(name);
var fields = ReflectionUtil.getFieldsRecursive(clazz);
for (var f : fields) {

View File

@@ -33,7 +33,7 @@ class ForeignKeyInitializer {
}
var otherEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.clazz());
if (otherEntity == null) {
EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) clazzAnnotation.clazz());
EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) clazzAnnotation.clazz(), f.getName());
otherEntity = mb.getEntity((Class<? extends SerializableObject>) clazzAnnotation.clazz());
PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity);
}
@@ -88,7 +88,7 @@ class ForeignKeyInitializer {
} else if (SerializableObject.class.isAssignableFrom(f.getType())) {
var otherEntity = mb.getEntity((Class<? extends SerializableObject>) f.getType());
if (otherEntity == null) {
EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) f.getType());
EntityInitializer.initEntity(mb, (Class<? extends SerializableObject>) f.getType(), f.getName());
otherEntity = mb.getEntity((Class<? extends SerializableObject>) f.getType());
PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity);
}

View File

@@ -6,16 +6,23 @@ import jef.model.annotations.processors.IndexProcessor;
import jef.model.annotations.processors.KeyProcessor;
import jef.model.annotations.processors.NotNullProcessor;
import jef.model.annotations.processors.UniqueProcessor;
import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint;
import jef.model.constraints.PrimaryKeyConstraint;
import jef.model.constraints.UniqueKeyConstraint;
import jef.serializable.SerializableObject;
import jef.util.Check;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public class ModelBuilder {
private static final List<AnnotationProcessor> annotationProcessors = new ArrayList<>();
static {
static {//TODO move this to a passable config class
annotationProcessors.add(NotNullProcessor.INSTANCE);
annotationProcessors.add(UniqueProcessor.INSTANCE);
annotationProcessors.add(IndexProcessor.INSTANCE);
@@ -23,7 +30,13 @@ public class ModelBuilder {
annotationProcessors.add(ForeignKeyProcessor.INSTANCE);
}
public static ModelBuilder from(Class<? extends DbContext> context) {
/**
* Initializes a ModelBuilder and configures the entities found in the context Class according to annotations.
*
* @param context the context to use for initialization
* @return an initialized ModelBuilder
*/
public static ModelBuilder from(Class<? extends DbContext> context) { //TODO pass optional config here
try {
return from0(context);
} catch (Exception e) {
@@ -40,15 +53,34 @@ public class ModelBuilder {
for (AnnotationProcessor processor : annotationProcessors) {
processor.apply(mb);
}
var instance = context.getDeclaredConstructor().newInstance();//TODO find constructor, //TODO optionally with config
instance.onModelCreate(mb);
return mb;
}
private final List<DbEntity<? extends SerializableObject>> entities;
public ModelBuilder(List<DbEntity<? extends SerializableObject>> entities) {
this.entities = entities;
/**
* Initializes an empty ModelBuilder
*/
public ModelBuilder() {
this(List.of());
}
/**
* Initializes a new ModelBuilder with the provided entities.
*/
public ModelBuilder(List<DbEntity<? extends SerializableObject>> entities) {
this.entities = new ArrayList<>(entities);
}
/**
* Returns an unmodifiable List of all entities.
*
* @return an unmodifiable List of all entities
*/
public List<DbEntity<? extends SerializableObject>> getEntities() {
return Collections.unmodifiableList(entities);
}
@@ -61,22 +93,157 @@ public class ModelBuilder {
* @return the database model for the requested class or null if not present.
*/
public <T extends SerializableObject> DbEntity<T> getEntity(Class<T> clazz) {
return (DbEntity<T>) entities.stream().filter(e -> e.getType() == clazz).findFirst().orElse(null);
Check.notNull(clazz, "clazz");
return getEntity(clazz.getName());
}
/**
* Returns the database model for the requested class or creates a new one if none exists.
* Returns the database model for the requested type name or null if not present.
*
* @param typeName the class name (including the package name) of the model class
* @param <T> the type of model class
* @return the database model for the requested class or null if not present.
*/
public <T extends SerializableObject> DbEntity<T> getEntity(String typeName) {
Check.notNull(typeName, "typeName");
return (DbEntity<T>) entities.stream().filter(e -> e.getTypeName().equals(typeName)).findFirst().orElse(null);
}
/**
* Returns the DbEntity for the requested class or creates a new empty DbEntity if none exists.
*
* @param clazz the class of the model class
* @param <T> the type of model class
* @return the database model for the requested class or the newly created one if none existed.
* @return the DbEntity for the requested class or the newly created empty DbEntity if none existed.
*/
public <T extends SerializableObject> DbEntity<T> getOrCreateEntity(Class<T> clazz) {
var entity = getEntity(clazz);
Check.notNull(clazz, "clazz");
return getOrCreateEntity(clazz.getName());
}
/**
* Returns the DbEntity for the requested class or creates a new empty DbEntity if none exists.
*
* @param typeName the class name (including package name) of the model class
* @param <T> the type of model class
* @return the DbEntity for the requested class or the newly created empty DbEntity if none existed.
*/
public <T extends SerializableObject> DbEntity<T> getOrCreateEntity(String typeName) {
Check.notNull(typeName, "typeName");
var entity = (DbEntity<T>) getEntity(typeName);
if (entity == null) {
entity = new DbEntity<>(clazz);
entity = new DbEntity<>(typeName);
entities.add(entity);
}
return entity;
}
/**
* Drops the entity from this ModelBuilder. This function also drops referencing foreign keys.
*
* @param entity the entity to drop
* @param <T> the type of the entity
* @return whether the entity was present in this ModelBuilder
*/
public <T extends SerializableObject> boolean dropEntity(DbEntity<T> entity) {
Check.notNull(entity, "entity");
var removed = this.entities.remove(entity);
if (!removed) {
return false;
}
for (DbEntity<? extends SerializableObject> e : this.entities) {
e.getForeignKeys().stream()
.filter(fk -> fk.getReferencedEntity() == entity)
.toList()
.forEach(e::dropForeignKey);
}
return removed;
}
public ModelBuilder clone() {
//copy entities
var entities = new ArrayList<DbEntity>();
for (int i = 0; i < this.entities.size(); i++) {
var old = this.entities.get(i);
var entity = (DbEntity<?>) new DbEntity(old.getTypeName());
entity.setName(old.getName());
entity.setType((Class) old.getType());
//add fields
old.getFields().stream().map(e -> {
var nf = new DbField(entity, e.getName(), e.getTypeName());
nf.setField(e.getField());
nf.setType(e.getType());
nf.setNotNull(e.isNotNull());
nf.setModelField(e.isModelField());
nf.setDatabaseField(e.isDatabaseField());
return nf;
})
.forEach(entity::addIfAbsent);
//apply exposed foreign keys
old.getFields().stream().filter(e -> e.getForeignKeyModelLink() != null).forEach(e -> {
var nf = entity.getFields().stream().filter(f -> f.getName().equals(e.getName())).findFirst().get(); //get(): should always be there
nf.setForeignKeyModelLink(old.getFields().stream().filter(f -> f.getName().equals(e.getForeignKeyModelLink().getName())).findFirst().get()); //get(): should always be there
});
entity.setName(old.getName());
if (old.getPrimaryKey() != null) {
var newPkFields = old.getPrimaryKey().getFields().stream()
.map(pkfield -> entity.getFields().stream().filter(f -> f.getName().equals(pkfield.getName())).findFirst().get()) //get(): should always be there
.toList();
entity.setPrimaryKey(new PrimaryKeyConstraint(entity, (List) newPkFields));
}
entities.add(entity);
}
//copy keys
for (int i = 0; i < entities.size(); i++) {
var entity = entities.get(i);
var old = this.entities.get(i);
for (ForeignKeyConstraint foreignKey : old.getForeignKeys()) {
var fkFields = foreignKey.getFields().stream()
.map(fkField -> entity.getFields().stream().filter(f -> ((DbField) f).getName().equals(fkField.getName())).findFirst().get()) //get(): should always be there
.toList();
var refEntity = entities.stream().filter(e -> e.getName().equals(foreignKey.getReferencedEntity().getName())).findFirst().get();//should always be there
var fkRefFields = foreignKey.getReferencedFields().stream()
.map(fkField -> refEntity.getFields().stream().filter(f -> ((DbField) f).getName().equals(fkField.getName())).findFirst().get()) //get(): should always be there
.toList();
entities.get(i).addForeignKey(new ForeignKeyConstraint(entity, (List) fkFields, refEntity, (List) fkRefFields, foreignKey.getOnUpdate(), foreignKey.getOnDelete()));
}
for (UniqueKeyConstraint uniqueKey : old.getUniqueKeys()) {
var newUkFields = uniqueKey.getFields().stream()
.map(ukField -> entity.getFields().stream().filter(f -> ((DbField) f).getName().equals(ukField.getName())).findFirst().get()) //get(): should always be there
.toList();
entity.addUniqueKey(new UniqueKeyConstraint(entity, (List) newUkFields));
}
for (KeyConstraint key : old.getKeys()) {
var newKFields = key.getFields().stream()
.map(kField -> entity.getFields().stream().filter(f -> ((DbField) f).getName().equals(kField.getName())).findFirst().get()) //get(): should always be there
.toList();
entity.addKey(new KeyConstraint(entity, (List) newKFields));
}
for (IndexConstraint index : old.getIndexes()) {
var newIFields = index.getFields().stream()
.map(iField -> entity.getFields().stream().filter(f -> ((DbField) f).getName().equals(iField.getName())).findFirst().get()) //get(): should always be there
.toList();
entity.addIndex(new IndexConstraint(entity, (List) newIFields));
}
}
return new ModelBuilder((List) entities);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ModelBuilder that = (ModelBuilder) o;
return entities.equals(that.entities);
}
@Override
public int hashCode() {
return Objects.hash(entities);
}
}

View File

@@ -3,13 +3,13 @@ package jef.model.annotations.processors;
import jef.model.DbEntity;
import jef.model.DbField;
import jef.model.annotations.Unique;
import jef.model.constraints.UniqueConstraint;
import jef.model.constraints.UniqueKeyConstraint;
import jef.serializable.SerializableObject;
import java.lang.annotation.Annotation;
import java.util.List;
public class UniqueProcessor extends KeyProcessorBase<UniqueConstraint, Unique> {
public class UniqueProcessor extends KeyProcessorBase<UniqueKeyConstraint, Unique> {
public static final UniqueProcessor INSTANCE = new UniqueProcessor();
private UniqueProcessor() {
@@ -17,13 +17,13 @@ public class UniqueProcessor extends KeyProcessorBase<UniqueConstraint, Unique>
}
@Override
protected UniqueConstraint initConstraint(DbEntity<? extends SerializableObject> entity, List<DbField<?>> fields) {
return new UniqueConstraint(entity, fields);
protected UniqueKeyConstraint initConstraint(DbEntity<? extends SerializableObject> entity, List<DbField<?>> fields) {
return new UniqueKeyConstraint(entity, fields);
}
@Override
protected void addConstraint(UniqueConstraint constr) {
constr.getEntity().addUniqueContstraint(constr);
protected void addConstraint(UniqueKeyConstraint constr) {
constr.getEntity().addUniqueKey(constr);
}
@Override

View File

@@ -0,0 +1,37 @@
package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import jef.util.Check;
import lombok.Getter;
import java.util.List;
import java.util.Objects;
@Getter
public abstract class ConstraintBase implements Constraint {
protected final DbEntity<?> entity;
protected final List<DbField<?>> fields;
public ConstraintBase(DbEntity<?> entity, List<DbField<?>> fields) {
this.entity = Check.notNull(entity, "entity");
this.fields = Check.notNull(fields, "fields");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ConstraintBase that = (ConstraintBase) o;
//only compare names
return entity.getName().equals(that.entity.getName())
&& fields.stream().map(DbField::getName).toList().equals(that.fields.stream().map(DbField::getName).toList());
}
@Override
public int hashCode() {
//only hash names
return Objects.hash(entity.getName(), fields.stream().map(DbField::getName).toList());
}
}

View File

@@ -2,24 +2,47 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class ForeignKeyConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
public class ForeignKeyConstraint extends ConstraintBase {
private final DbEntity<?> referencedEntity;
private final List<DbField<?>> referencedFields;
private final Action onUpdate;
private final Action onDelete;
public ForeignKeyConstraint(DbEntity<?> entity, List<DbField<?>> fields, DbEntity<?> referencedEntity, List<DbField<?>> referencedFields, Action onUpdate, Action onDelete) {
super(entity, fields);
this.referencedEntity = referencedEntity;
this.referencedFields = referencedFields;
this.onUpdate = onUpdate;
this.onDelete = onDelete;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
ForeignKeyConstraint that = (ForeignKeyConstraint) o;
//only compare names
return referencedEntity.getName().equals(that.referencedEntity.getName())
&& referencedFields.stream().map(DbField::getName).toList().equals(that.referencedFields.stream().map(DbField::getName).toList())
&& onUpdate == that.onUpdate
&& onDelete == that.onDelete;
}
@Override
public int hashCode() {
//only hash names
return Objects.hash(super.hashCode(), referencedEntity.getName(), referencedFields.stream().map(DbField::getName).toList(), onUpdate, onDelete);
}
@Override
public String getName() {
return "FK_" + entity.getName() + "_" + referencedEntity.getName() + "_" + fields.stream().map(DbField::getName).collect(Collectors.joining("_"));
@@ -28,7 +51,7 @@ public class ForeignKeyConstraint implements Constraint {
@Override
public String toString() {
return "CONSTRAINT " + getName() + " FOREIGN KEY (" + fields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ") "
+ "REFERENCES " + referencedEntity.getName() + "(" + referencedFields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
+ "REFERENCES " + referencedEntity.getName() + "(" + referencedFields.stream().map(DbField::getName).collect(Collectors.joining(", ")) + ")";
}
public enum Action {
@@ -36,5 +59,6 @@ public class ForeignKeyConstraint implements Constraint {
RESTRICT,
SET_NULL,
NO_ACTION,
;
}
}

View File

@@ -2,7 +2,6 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -10,11 +9,11 @@ import java.util.List;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class IndexConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
@EqualsAndHashCode(callSuper = true)
public class IndexConstraint extends ConstraintBase {
public IndexConstraint(DbEntity<?> entity, List<DbField<?>> fields) {
super(entity, fields);
}
@Override
public String getName() {

View File

@@ -2,7 +2,6 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -10,11 +9,11 @@ import java.util.List;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class KeyConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
@EqualsAndHashCode(callSuper = true)
public class KeyConstraint extends ConstraintBase {
public KeyConstraint(DbEntity<?> entity, List<DbField<?>> fields) {
super(entity, fields);
}
@Override
public String getName() {

View File

@@ -2,7 +2,6 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -10,11 +9,11 @@ import java.util.List;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class PrimaryKeyConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
@EqualsAndHashCode(callSuper = true)
public class PrimaryKeyConstraint extends ConstraintBase {
public PrimaryKeyConstraint(DbEntity<?> entity, List<DbField<?>> fields) {
super(entity, fields);
}
@Override
public String getName() {

View File

@@ -2,7 +2,6 @@ package jef.model.constraints;
import jef.model.DbEntity;
import jef.model.DbField;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -10,11 +9,11 @@ import java.util.List;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
@EqualsAndHashCode
public class UniqueConstraint implements Constraint {
private final DbEntity<?> entity;
private final List<DbField<?>> fields;
@EqualsAndHashCode(callSuper = true)
public class UniqueKeyConstraint extends ConstraintBase {
public UniqueKeyConstraint(DbEntity<?> entity, List<DbField<?>> fields) {
super(entity, fields);
}
@Override
public String getName() {

View File

@@ -0,0 +1,7 @@
package jef.model.migration;
public interface Migration {
void up(MigrationBuilder migrationBuilder);
void down(MigrationBuilder migrationBuilder);
}

View File

@@ -0,0 +1,102 @@
package jef.model.migration;
import jef.model.constraints.ForeignKeyConstraint;
import jef.model.migration.operation.AddFieldOperation;
import jef.model.migration.operation.AddForeignKeyOperation;
import jef.model.migration.operation.AddIndexOperation;
import jef.model.migration.operation.AddKeyOperation;
import jef.model.migration.operation.AddPrimaryKeyOperation;
import jef.model.migration.operation.AddTableOperation;
import jef.model.migration.operation.AddUniqueKeyOperation;
import jef.model.migration.operation.DropConstraintOperation;
import jef.model.migration.operation.DropFieldOperation;
import jef.model.migration.operation.DropTableOperation;
import jef.model.migration.operation.MigrationOperation;
import jef.model.migration.operation.RenameFieldOperation;
import jef.model.migration.operation.RenameTableOperation;
import jef.model.migration.operation.UpdateFieldOperation;
import java.util.ArrayList;
import java.util.List;
public class MigrationBuilder {
private final List<MigrationOperation.Builder<?>> operations = new ArrayList<>();
public AddTableOperation.Builder addTable(String table, List<AddFieldOperation.Builder> fields) {
var op = new AddTableOperation.Builder(table, fields);
operations.add(op);
return op;
}
public RenameTableOperation.Builder renameTable(String oldName, String newName) {
var op = new RenameTableOperation.Builder(oldName, newName);
operations.add(op);
return op;
}
public DropTableOperation.Builder dropTable(String table) {
var op = new DropTableOperation.Builder(table);
operations.add(op);
return op;
}
public AddFieldOperation.Builder addField(String table, String field) {
var op = new AddFieldOperation.Builder(table, field);
operations.add(op);
return op;
}
public RenameFieldOperation.Builder renameField(String table, String oldName, String newName) {
var op = new RenameFieldOperation.Builder(table, oldName, newName);
operations.add(op);
return op;
}
public UpdateFieldOperation.Builder updateField(String table, String field) {
var op = new UpdateFieldOperation.Builder(table, field);
operations.add(op);
return op;
}
public DropFieldOperation.Builder dropField(String table, String field) {
var op = new DropFieldOperation.Builder(table, field);
operations.add(op);
return op;
}
public AddPrimaryKeyOperation.Builder addPrimaryKey(String name, String table, List<String> fields) {
var op = new AddPrimaryKeyOperation.Builder(name, table, fields);
operations.add(op);
return op;
}
public AddForeignKeyOperation.Builder addForeignKey(String name, String table, List<String> fields, String referencedTable, List<String> referencedFields, ForeignKeyConstraint.Action onUpdate, ForeignKeyConstraint.Action onDelete) {
var op = new AddForeignKeyOperation.Builder(name, table, fields, referencedTable, referencedFields, onUpdate, onDelete);
operations.add(op);
return op;
}
public AddUniqueKeyOperation.Builder addUniqueKey(String name, String table, List<String> fields) {
var op = new AddUniqueKeyOperation.Builder(name, table, fields);
operations.add(op);
return op;
}
public AddKeyOperation.Builder addKey(String name, String table, List<String> fields) {
var op = new AddKeyOperation.Builder(name, table, fields);
operations.add(op);
return op;
}
public AddIndexOperation.Builder addIndex(String name, String table, List<String> fields) {
var op = new AddIndexOperation.Builder(name, table, fields);
operations.add(op);
return op;
}
public DropConstraintOperation.Builder dropConstraint(String name, String table) {
var op = new DropConstraintOperation.Builder(name, table);
operations.add(op);
return op;
}
}

View File

@@ -0,0 +1,195 @@
package jef.model.migration.creator;
import jef.model.constraints.ForeignKeyConstraint;
import jef.model.migration.Migration;
import jef.model.migration.MigrationBuilder;
import jef.model.migration.operation.AddFieldOperation;
import jef.model.migration.operation.AddForeignKeyOperation;
import jef.model.migration.operation.AddIndexOperation;
import jef.model.migration.operation.AddKeyOperation;
import jef.model.migration.operation.AddPrimaryKeyOperation;
import jef.model.migration.operation.AddTableOperation;
import jef.model.migration.operation.AddUniqueKeyOperation;
import jef.model.migration.operation.DropConstraintOperation;
import jef.model.migration.operation.DropFieldOperation;
import jef.model.migration.operation.DropTableOperation;
import jef.model.migration.operation.MigrationOperation;
import jef.model.migration.operation.RenameFieldOperation;
import jef.model.migration.operation.RenameTableOperation;
import jef.model.migration.operation.UpdateFieldOperation;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class MigrationBuilderGenerator {
private final List<MigrationOperation> opsUp;
private final List<MigrationOperation> opsDown;
private final String name;
private final String packageName;
private final Set<Class<?>> imports = new HashSet<>();
@Getter
private String java = null;
public MigrationBuilderGenerator generate() {
if (java == null) {
java = generateMigrationBuilderJava();
}
return this;
}
private String generateMigrationBuilderJava() {
imports.add(Migration.class);
imports.add(MigrationBuilder.class);
//generate java for migration step and concat
var migrationUp = String.join("\n\n", opsUp.stream().map(this::getMigrationJava).toList());
var migrationDown = String.join("\n\n", opsDown.stream().map(this::getMigrationJava).toList());
//generate migration class file
var normalImports = imports.stream().filter(e -> !e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
var javaImports = imports.stream().filter(e -> e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
var java = "package " + packageName + ";\n"
+ "\n"
+ normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
+ javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
+ "public class " + name + " implements Migration {\n"
+ " public void up(MigrationBuilder mb) {\n"
+ " " + migrationUp.replace("\n", "\n ") + "\n"
+ " }\n"
+ "\n"
+ " public void down(MigrationBuilder mb) {\n"
+ " " + migrationDown.replace("\n", "\n ") + "\n"
+ " }\n"
+ "}\n";
return java;
}
//mapper stuff
private final Map<Class<? extends MigrationOperation>, Function<? extends MigrationOperation, String>> OP_TO_STRING_MAPPERS = initMapper();
private final Function<MigrationOperation, String> UNSUPPORTED_MIGRATION_OPERATION_FUNCTION = (MigrationOperation i) -> {
throw new RuntimeException(new UnsupportedOperationException("Unsupported migration operation: " + i.getClass().getSimpleName()));
};
private Map<Class<? extends MigrationOperation>, Function<? extends MigrationOperation, String>> initMapper() {
Map<Class<? extends MigrationOperation>, Function<? extends MigrationOperation, String>> map = new HashMap<>();
map.put(AddFieldOperation.class, (AddFieldOperation op) -> addFieldOp(op));
map.put(AddForeignKeyOperation.class, (AddForeignKeyOperation op) -> addForeignKeyOp(op));
map.put(AddIndexOperation.class, (AddIndexOperation op) -> addIndexOp(op));
map.put(AddKeyOperation.class, (AddKeyOperation op) -> addKeyOp(op));
map.put(AddPrimaryKeyOperation.class, (AddPrimaryKeyOperation op) -> addPrimaryKeyOp(op));
map.put(AddTableOperation.class, (AddTableOperation op) -> addTableOp(op));
map.put(AddUniqueKeyOperation.class, (AddUniqueKeyOperation op) -> addUniqueKeyOp(op));
map.put(DropConstraintOperation.class, (DropConstraintOperation op) -> dropConstraintOp(op));
map.put(DropFieldOperation.class, (DropFieldOperation op) -> dropFieldOp(op));
map.put(DropTableOperation.class, (DropTableOperation op) -> dropTableOp(op));
map.put(RenameFieldOperation.class, (RenameFieldOperation op) -> renameFieldOp(op));
map.put(RenameTableOperation.class, (RenameTableOperation op) -> renameTableOp(op));
map.put(UpdateFieldOperation.class, (UpdateFieldOperation op) -> updateField(op));
return map;
}
private String getMigrationJava(MigrationOperation migrationOperation) {
var mapper = ((Function<MigrationOperation, String>)
OP_TO_STRING_MAPPERS.getOrDefault(migrationOperation.getClass(), UNSUPPORTED_MIGRATION_OPERATION_FUNCTION));
return mapper.apply(migrationOperation);
}
private String addFieldOp(AddFieldOperation op) {
return "mb.addField(\"" + op.getTable() + "\", \"" + op.getField() + "\")" + addFieldOpOptional(op) + ";";
}
private String addFieldOpOptional(AddFieldOperation op) {
return "\n"
+ " .notNull(" + op.isNotNull() + ")\n"
+ " .sqlType(\"" + op.getSqlType() + "\")";
}
private String addForeignKeyOp(AddForeignKeyOperation op) {
imports.add(List.class);
imports.add(ForeignKeyConstraint.class);
imports.add(ForeignKeyConstraint.Action.class);
return "mb.addForeignKey(\"" + op.getName() + "\",\n"
+ " \"" + op.getTable() + "\",\n"
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "),\n"
+ " \"" + op.getReferencedTable() + "\",\n"
+ " List.of(" + op.getReferencedFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "),\n"
+ " ForeignKeyConstraint.Action." + op.getOnUpdate().name() + ",\n"
+ " ForeignKeyConstraint.Action." + op.getOnDelete().name() + ");";
}
private String addIndexOp(AddIndexOperation op) {
imports.add(List.class);
return "mb.addIndex(\"" + op.getName() + "\",\n"
+ " \"" + op.getTable() + "\",\n"
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "));";
}
private String addKeyOp(AddKeyOperation op) {
imports.add(List.class);
return "mb.addKey(\"" + op.getName() + "\",\n"
+ " \"" + op.getTable() + "\",\n"
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "));";
}
private String addPrimaryKeyOp(AddPrimaryKeyOperation op) {
imports.add(List.class);
return "mb.addPrimaryKey(\"" + op.getName() + "\",\n"
+ " \"" + op.getTable() + "\",\n"
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "));";
}
private String addTableOp(AddTableOperation op) {
imports.add(List.class);
imports.add(AddFieldOperation.class);
imports.add(AddFieldOperation.Builder.class);
return "mb.addTable(\"" + op.getTable() + "\", List.of(\n"
+ op.getFields().stream()
/**/.map(f -> " new AddFieldOperation.Builder(\"" + op.getTable() + "\", \"" + f.build().getField() + "\")" + addFieldOpOptional(f.build()).replace("\n", "\n "))
/**/.collect(Collectors.joining(",\n")) + "\n"
+ "));";
}
private String addUniqueKeyOp(AddUniqueKeyOperation op) {
imports.add(List.class);
return "mb.addUniqueKey(\"" + op.getName() + "\",\n"
+ " \"" + op.getTable() + "\",\n"
+ " List.of(" + op.getFields().stream().map(e -> "\"" + e + "\"").collect(Collectors.joining(", ")) + "));";
}
private String dropConstraintOp(DropConstraintOperation op) {
return "mb.dropConstraint(\"" + op.getTable() + "\", \"" + op.getName() + "\");";
}
private String dropFieldOp(DropFieldOperation op) {
return "mb.dropField(\"" + op.getTable() + "\", \"" + op.getField() + "\");";
}
private String dropTableOp(DropTableOperation op) {
return "mb.dropTable(\"" + op.getTable() + "\");";
}
private String renameFieldOp(RenameFieldOperation op) {
return "mb.renameField(\"" + op.getTable() + "\", \"" + op.getOldName() + "\", \"" + op.getNewName() + "\");";
}
private String renameTableOp(RenameTableOperation op) {
return "mb.renameTable(\"" + op.getOldName() + "\", \"" + op.getNewName() + "\");";
}
private String updateField(UpdateFieldOperation op) {
return "mb.updateField(\"" + op.getTable() + "\", \"" + op.getField() + "\")\n"
+ (op.getNewName() != null ? " .newName(\"" + op.getNewName() + "\")\n" : "")
+ " .notNull(" + op.isNotNull() + ")\n"
+ " .sqlType(" + op.getSqlType() + ");";
}
}

View File

@@ -0,0 +1,487 @@
package jef.model.migration.creator;
import jef.model.DbField;
import jef.model.ModelBuilder;
import jef.model.ModelException;
import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint;
import jef.model.constraints.UniqueKeyConstraint;
import jef.model.migration.operation.AddFieldOperation;
import jef.model.migration.operation.AddForeignKeyOperation;
import jef.model.migration.operation.AddIndexOperation;
import jef.model.migration.operation.AddKeyOperation;
import jef.model.migration.operation.AddPrimaryKeyOperation;
import jef.model.migration.operation.AddTableOperation;
import jef.model.migration.operation.AddUniqueKeyOperation;
import jef.model.migration.operation.DropConstraintOperation;
import jef.model.migration.operation.DropFieldOperation;
import jef.model.migration.operation.DropTableOperation;
import jef.model.migration.operation.MigrationOperation;
import jef.model.migration.operation.RenameFieldOperation;
import jef.model.migration.operation.RenameTableOperation;
import jef.model.migration.operation.UpdateFieldOperation;
import jef.util.Check;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class MigrationCreator {
private static final String ILLEGAL_CHARACTERS = "\"'`,.";//TODO also validate this in modelbuilder/dbentity/dbfield
public Result createMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, String currentSnapshotJava) {
var result = new Result();
//create pre-migration model snapshot class
result = generatePreMigrationSnapshot(name, packageName, currentSnapshotJava, result);
//create migration
result = generateMigration(from, to, name, packageName, result);
//create current model snapshot class
result = generatePostMigrationSnapshot(to, packageName, result);
return result;
}
private Result generatePreMigrationSnapshot(String name, String packageName, String currentSnapshotJava, Result result) {
String preMigrationSnapshot;
if (currentSnapshotJava == null || currentSnapshotJava.isBlank()) {
preMigrationSnapshot = generateModelBuilderJava(new ModelBuilder(), name, packageName);
} else {
preMigrationSnapshot = currentSnapshotJava.replace("CurrentSnapshot", name + "Snapshot");
}
result.setMigrationSnapshot(preMigrationSnapshot);
return result;
}
private Result generatePostMigrationSnapshot(ModelBuilder to, String packageName, Result result) {
result.setCurrentSnapshot(generateModelBuilderJava(to, "Current", packageName));
return result;
}
private String generateModelBuilderJava(ModelBuilder mb, String name, String packageName) {
return new ModelBuilderGenerator(mb, name, packageName).generate().getJava();
}
private Result generateMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, Result result) {
//check for illegal characters
for (var entity : to.getEntities()) {
if (entity.getName().matches("[" + ILLEGAL_CHARACTERS + "]")) {
throw new ModelException("Entity " + entity.getType().getSimpleName() + " has illegal characters in its name: " + entity.getName());
}
for (var field : entity.getFields()) {
if (field.getName().matches("[" + ILLEGAL_CHARACTERS + "]")) {
throw new ModelException("Field " + entity.getType().getSimpleName() + "::" + field.getField().getName() + " has illegal characters in its name: " + entity.getName());
}
}
}
//reduce model builders to changes
var mcd = new ModelChangeDetector(from, to).detect();
var fromReduced = mcd.getFrom();
var toReduced = mcd.getTo();
//create migration steps
var migrationUpOps = new ArrayList<MigrationOperation.Builder>();
var migrationDownOps = new ArrayList<MigrationOperation.Builder>();
generateMigrationSteps(fromReduced, toReduced, from, to, migrationUpOps);
generateMigrationSteps(toReduced, fromReduced, to, from, migrationDownOps);
var builtUpOps = migrationUpOps.stream().map(e -> e.build()).toList();
var builtDownOps = migrationDownOps.stream().map(e -> e.build()).toList();
result.setMigration(new MigrationBuilderGenerator(builtUpOps, builtDownOps, name, packageName).generate().getJava());
result.setStepsUp(builtUpOps);
result.setStepsDown(builtDownOps);
return result;
}
private void generateMigrationSteps(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
//key drop
addForeignKeyDropGeneration(fromReduced, toReduced, from, to, steps);
addPrimaryKeyDropGeneration(fromReduced, toReduced, from, to, steps);
addUniqueKeyDropGeneration(fromReduced, toReduced, from, to, steps);
addKeyDropGeneration(fromReduced, toReduced, from, to, steps);
addIndexDropGeneration(fromReduced, toReduced, from, to, steps);
//table
addTableDropGeneration(fromReduced, toReduced, from, to, steps);
addTableRenameGeneration(fromReduced, toReduced, from, to, steps);
addTableAddGeneration(fromReduced, toReduced, from, to, steps);
//fields
addFieldAddRenameUpdateDropGeneration(fromReduced, toReduced, from, to, steps);
//key add
addPrimaryKeyAddGeneration(fromReduced, toReduced, from, to, steps);
addForeignKeyAddGeneration(fromReduced, toReduced, from, to, steps);
addUniqueKeyAddGeneration(fromReduced, toReduced, from, to, steps);
addKeyAddGeneration(fromReduced, toReduced, from, to, steps);
addIndexAddGeneration(fromReduced, toReduced, from, to, steps);
}
private void addTableAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var toEntity : toReduced.getEntities()) {
var fromEntity = fromReduced.getEntity(toEntity.getTypeName());
//new entity
if (fromEntity == null) {
steps.add(new AddTableOperation.Builder(
toEntity.getName(),
toEntity.getFields().stream()
.filter(DbField::isDatabaseField)
.map(e -> new AddFieldOperation.Builder(toEntity.getName(), e.getName())
.notNull(e.isNotNull())
.sqlType("TODO"))
.toList()
));
}
}
}
private void addTableRenameGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var toEntity : toReduced.getEntities()) {
var fromEntity = fromReduced.getEntity(toEntity.getTypeName());
// entity added
if (fromEntity == null) {
continue;
}
//entity rename
if (!fromEntity.getName().equals(toEntity.getName())) {
steps.add(new RenameTableOperation.Builder(fromEntity.getName(), toEntity.getName()));
}
}
}
private void addTableDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var fromEntity : fromReduced.getEntities()) {
var toEntity = toReduced.getEntity(fromEntity.getTypeName());
if (toEntity == null) {
steps.add(new DropTableOperation.Builder(fromEntity.getName()));
}
}
}
private void addFieldAddRenameUpdateDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var toEntity : toReduced.getEntities()) {
var fromEntity = fromReduced.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
//entity added -> nothing to do here
if (fromEntity == null) {
continue;
}
var remainingFromFields = new ArrayList<>(fromEntity.getFields());
var remainingToFields = new ArrayList<>(toEntity.getFields());
var handledFrom = new ArrayList<DbField<?>>();
var handledTo = new ArrayList<DbField<?>>();
//fields with same name but different type parameters
for (DbField<?> toField : toEntity.getFields()) {
var fromField = fromEntity.getFields().stream().filter(e -> e.getName().equals(toField.getName())).findFirst().orElse(null);
if (fromField != null) {
handledFrom.add(fromField);
handledTo.add(toField);
//this assumes the reduced ModelBuilders exclude exactly matching entities
steps.add(new UpdateFieldOperation.Builder(toField.getEntity().getName(), toField.getName())
.notNull(toField.isNotNull()));
}
}
//fields with different name but same type parameters
remainingFromFields.removeAll(handledFrom);
remainingToFields.removeAll(handledTo);
var map = new ArrayList<FieldCompare>();
for (DbField<?> toField : remainingToFields) {
for (DbField<?> FromField : remainingFromFields) {
map.add(new FieldCompare(FromField, toField));
}
}
map.removeIf(e -> e.getSimilarity() <= 0);
map.sort(Comparator.comparingInt(FieldCompare::getSimilarity).reversed());
for (FieldCompare compare : map) {
var toField = compare.getTo();
var fromField = compare.getFrom();
if (handledFrom.contains(fromField) || handledTo.contains(toField)) {
continue;
}
handledFrom.add(fromField);
handledTo.add(toField);
steps.add(new RenameFieldOperation.Builder(toField.getEntity().getName(), fromField.getName(), toField.getName()));
}
//drop
remainingFromFields.removeAll(handledFrom);
remainingToFields.removeAll(handledTo);
for (DbField<?> fromField : remainingFromFields) {
var toField = remainingToFields.stream().filter(e -> e.getName().equals(fromField.getName())).findFirst().orElse(null);
if (toField == null) {
handledFrom.add(fromField);
steps.add(new DropFieldOperation.Builder(fromField.getEntity().getName(), fromField.getName()));
}
}
//add
remainingFromFields.removeAll(handledFrom);
remainingToFields.removeAll(handledTo);
for (DbField<?> toField : remainingToFields) {
var fromField = remainingFromFields.stream().filter(e -> e.getName().equals(toField.getName())).findFirst().orElse(null);
if (fromField == null) {
handledTo.add(toField);
steps.add(new AddFieldOperation.Builder(toField.getEntity().getName(), toField.getName())
.notNull(toField.isNotNull()));
}
}
}
}
private void addPrimaryKeyAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var toEntity : toReduced.getEntities()) {
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = toEntity.getFields();
var involvedForeignKeys = originalEntity.getPrimaryKey() != null
&& originalEntity.getPrimaryKey().getFields().stream().anyMatch(involvedFields::contains);
if (involvedForeignKeys) {
steps.add(new AddPrimaryKeyOperation.Builder(
originalEntity.getPrimaryKey().getName(),
originalEntity.getPrimaryKey().getEntity().getName(),
originalEntity.getPrimaryKey().getFields().stream().map(DbField::getName).collect(Collectors.toList())));
}
}
}
private void addForeignKeyAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var toEntity : toReduced.getEntities()) {
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = toEntity.getFields();
var involvedForeignKeys = originalEntity.getForeignKeys().stream()
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || toEntity.getForeignKeys().contains(e))
.toList();
for (ForeignKeyConstraint foreignKey : involvedForeignKeys) {
steps.add(new AddForeignKeyOperation.Builder(
foreignKey.getName(),
foreignKey.getEntity().getName(),
foreignKey.getFields().stream().map(DbField::getName).collect(Collectors.toList()),
foreignKey.getReferencedEntity().getName(),
foreignKey.getReferencedFields().stream().map(DbField::getName).collect(Collectors.toList()),
foreignKey.getOnUpdate(), foreignKey.getOnDelete()));
}
}
}
private void addUniqueKeyAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var toEntity : toReduced.getEntities()) {
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = toEntity.getFields();
var involvedUniqueKeys = originalEntity.getUniqueKeys().stream()
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || toEntity.getUniqueKeys().contains(e))
.toList();
for (UniqueKeyConstraint uniqueKey : involvedUniqueKeys) {
steps.add(new AddUniqueKeyOperation.Builder(
uniqueKey.getName(),
uniqueKey.getEntity().getName(),
uniqueKey.getFields().stream().map(DbField::getName).collect(Collectors.toList())));
}
}
}
private void addKeyAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var toEntity : toReduced.getEntities()) {
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = toEntity.getFields();
var involvedKeys = originalEntity.getKeys().stream()
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || toEntity.getKeys().contains(e))
.toList();
for (KeyConstraint key : involvedKeys) {
steps.add(new AddKeyOperation.Builder(
key.getName(),
key.getEntity().getName(),
key.getFields().stream().map(DbField::getName).collect(Collectors.toList())));
}
}
}
private void addIndexAddGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var toEntity : toReduced.getEntities()) {
var originalEntity = to.getEntities().stream().filter(e -> e.getName().equals(toEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = toEntity.getFields();
var involvedIndexes = originalEntity.getIndexes().stream()
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || toEntity.getIndexes().contains(e))
.toList();
for (IndexConstraint index : involvedIndexes) {
steps.add(new AddIndexOperation.Builder(
index.getName(),
index.getEntity().getName(),
index.getFields().stream().map(DbField::getName).collect(Collectors.toList())));
}
}
}
private void addPrimaryKeyDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var fromEntity : fromReduced.getEntities()) {
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = fromEntity.getFields();
if (originalEntity.getPrimaryKey() == null) {
continue;
}
var isPrimaryKeyInvolved = originalEntity.getPrimaryKey().getFields().stream().anyMatch(involvedFields::contains);
if (isPrimaryKeyInvolved) {
//drop all referencing foreign keys
from.getEntities().stream().flatMap(e -> e.getForeignKeys().stream())
.filter(foreignKey -> foreignKey.getReferencedEntity() == fromEntity
&& foreignKey.getReferencedFields().stream().anyMatch(involvedFields::contains))
.forEach(foreignKey -> steps.add(new DropConstraintOperation.Builder(foreignKey.getName(), fromEntity.getName())));
//drop primary
steps.add(new DropConstraintOperation.Builder(originalEntity.getPrimaryKey().getName(), fromEntity.getName()));
}
}
}
private void addForeignKeyDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var fromEntity : fromReduced.getEntities()) {
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = fromEntity.getFields();
var involvedForeignKeys = originalEntity.getForeignKeys().stream()
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || fromEntity.getForeignKeys().contains(e))
.toList();
for (ForeignKeyConstraint foreignKey : involvedForeignKeys) {
steps.add(new DropConstraintOperation.Builder(foreignKey.getName(), fromEntity.getName()));
}
}
// for (var fromEntity : from.getEntities()) {
// var toEntity = to.getEntity(fromEntity.getType());
//
// //foreign keys
// for (var foreignKey : fromEntity.getForeignKeys()) {
// if (toEntity == null || toEntity.getForeignKeys().stream().noneMatch(fk -> compareForeignKey(foreignKey, fk))) {
// migrationUpOps.add(new DropConstraintOperation.Builder(foreignKey.getName(), fromEntity.getName()));
// }
// }
// }
}
private void addUniqueKeyDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var fromEntity : fromReduced.getEntities()) {
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = fromEntity.getFields();
var involvedForeignKeys = originalEntity.getUniqueKeys().stream()
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || fromEntity.getUniqueKeys().contains(e))
.toList();
for (UniqueKeyConstraint uniqueKey : involvedForeignKeys) {
steps.add(new DropConstraintOperation.Builder(uniqueKey.getName(), fromEntity.getName()));
}
}
// for (var fromEntity : from.getEntities()) {
// var toEntity = to.getEntity(fromEntity.getType());
//
// //unique keys
// for (var uniqueKey : fromEntity.getUniqueKeys()) {
// if (toEntity == null || toEntity.getUniqueKeys().stream().noneMatch(uk -> compareConstraint(uniqueKey, uk))) {
// steps.add(new DropConstraintOperation.Builder(uniqueKey.getName(), fromEntity.getName()));
// }
// }
// }
}
private void addKeyDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var fromEntity : fromReduced.getEntities()) {
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = fromEntity.getFields();
var involvedForeignKeys = originalEntity.getKeys().stream()
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || fromEntity.getKeys().contains(e))
.toList();
for (KeyConstraint key : involvedForeignKeys) {
steps.add(new DropConstraintOperation.Builder(key.getName(), fromEntity.getName()));
}
}
// for (var fromEntity : from.getEntities()) {
// var toEntity = to.getEntity(fromEntity.getType());
//
// //keys
// for (var key : fromEntity.getKeys()) {
// if (toEntity == null || toEntity.getKeys().stream().noneMatch(k -> compareConstraint(key, k))) {
// migrationUpOps.add(new DropConstraintOperation.Builder(key.getName(), fromEntity.getName()));
// }
// }
// }
}
private void addIndexDropGeneration(ModelBuilder fromReduced, ModelBuilder toReduced, ModelBuilder from, ModelBuilder to, List<MigrationOperation.Builder> steps) {
for (var fromEntity : fromReduced.getEntities()) {
var originalEntity = from.getEntities().stream().filter(e -> e.getName().equals(fromEntity.getName())).findFirst().orElse(null);
Check.notNull(originalEntity, "originalEntity"); //may never be null
var involvedFields = fromEntity.getFields();
var involvedForeignKeys = originalEntity.getIndexes().stream()
.filter(e -> e.getFields().stream().anyMatch(involvedFields::contains) || fromEntity.getIndexes().contains(e))
.toList();
for (IndexConstraint foreignKey : involvedForeignKeys) {
steps.add(new DropConstraintOperation.Builder(foreignKey.getName(), fromEntity.getName()));
}
}
// for (var fromEntity : from.getEntities()) {
// var toEntity = to.getEntity(fromEntity.getType());
//
// //indexes
// for (var index : fromEntity.getIndexes()) {
// if (toEntity == null || toEntity.getIndexes().stream().noneMatch(i -> compareConstraint(index, i))) {
// steps.add(new DropConstraintOperation.Builder(index.getName(), fromEntity.getName()));
// }
// }
// }
}
@Getter
@Setter(AccessLevel.PACKAGE)
@NoArgsConstructor(access = AccessLevel.PACKAGE)
public static class Result {
private List<MigrationOperation> stepsUp = new ArrayList<>();
private List<MigrationOperation> stepsDown = new ArrayList<>();
private String migration = "";
private String migrationSnapshot = "";
private String currentSnapshot = "";
}
@Getter
private class FieldCompare {
private final DbField<?> from;
private final DbField<?> to;
private final int similarity;
public FieldCompare(DbField<?> from, DbField<?> to) {
this.from = from;
this.to = to;
this.similarity = compute();
}
private int compute() {
return from.equalsExceptName(to) ? 1 : 0;
}
}
}

View File

@@ -0,0 +1,124 @@
package jef.model.migration.creator;
import jef.model.DbContext;
import jef.model.DbEntity;
import jef.model.DbField;
import jef.model.ModelBuilder;
import jef.model.constraints.ForeignKeyConstraint;
import jef.model.constraints.IndexConstraint;
import jef.model.constraints.KeyConstraint;
import jef.model.constraints.PrimaryKeyConstraint;
import jef.model.constraints.UniqueKeyConstraint;
import jef.serializable.SerializableObject;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class ModelBuilderGenerator {
private final ModelBuilder mb;
private final String name;
private final String packageName;
private final Set<Class<?>> imports = new HashSet<>();
@Getter
private String java = null;
public ModelBuilderGenerator generate() {
if (java == null) {
java = generateModelBuilderJava();
}
return this;
}
private String generateModelBuilderJava() {
var indent = " ";
imports.add(DbContext.class);
imports.add(ModelBuilder.class);
imports.add(DbEntity.class);
var java = ""
+ "public class " + name + "Snapshot extends DbContext {\n"
+ " @Override\n"
+ " public void onModelCreate(ModelBuilder mb) {\n"
+ indent + "DbEntity entity;\n"
+ indent + "DbEntity referencedEntity;\n";
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
java += indent + "entity = mb.getOrCreateEntity(\"" + entity.getTypeName() + "\");\n"
+ indent + "entity.setName(\"" + entity.getName() + "\");\n";
for (DbField<?> field : entity.getFields()) {
java += indent + "entity.getOrCreateField(\"" + field.getName() + "\", \"" + field.getTypeName() + "\")"
+ (field.isNotNull() ? "\n" + indent + " .setNotNull(true)" : "")
+ "\n" + indent + " .setDatabaseField(" + field.isDatabaseField() + ")"
+ "\n" + indent + " .setModelField(" + field.isModelField() + ")"
+ ";\n";
}
if (entity.getPrimaryKey() != null) {
imports.add(List.class);
imports.add(PrimaryKeyConstraint.class);
java += indent + "entity.setPrimaryKey(new PrimaryKeyConstraint(entity, List.of(\n"
+ indent + " " + entity.getPrimaryKey().getFields().stream().map(f -> "entity.getField(\"" + f.getName() + "\")").collect(Collectors.joining(",\n ")) + "\n"
+ indent + " )));\n";
}
java += "\n";
}
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
if (entity.getForeignKeys().isEmpty() && entity.getForeignKeys().isEmpty() && entity.getForeignKeys().isEmpty() && entity.getForeignKeys().isEmpty()) {
continue;
}
java += indent + "entity = mb.getOrCreateEntity(\"" + entity.getTypeName() + "\");\n";
for (ForeignKeyConstraint foreignKey : entity.getForeignKeys()) {
imports.add(List.class);
imports.add(ForeignKeyConstraint.class);
imports.add(ForeignKeyConstraint.Action.class);
java += indent + "referencedEntity = mb.getEntity(\"" + foreignKey.getReferencedEntity().getTypeName() + "\");\n"
+ indent + "entity.addForeignKey(new ForeignKeyConstraint(entity, List.of(\n"
+ indent + " " + foreignKey.getFields().stream().map(f -> "entity.getField(\"" + f.getName() + "\")").collect(Collectors.joining(",\n " + indent)) + "\n"
+ indent + " ),\n"
+ indent + " referencedEntity, List.of(\n"
+ indent + " " + foreignKey.getReferencedFields().stream().map(f -> "referencedEntity.getField(\"" + f.getName() + "\")").collect(Collectors.joining(",\n " + indent)) + "\n"
+ indent + " ),\n"
+ indent + " ForeignKeyConstraint.Action." + foreignKey.getOnUpdate().name() + ", ForeignKeyConstraint.Action." + foreignKey.getOnDelete().name() + "));\n";
}
for (UniqueKeyConstraint uniqueKey : entity.getUniqueKeys()) {
imports.add(List.class);
imports.add(UniqueKeyConstraint.class);
java += indent + "entity.addUniqueKey(new UniqueKeyConstraint(entity, List.of(\n"
+ indent + " " + uniqueKey.getFields().stream().map(f -> "entity.getField(\"" + f.getName() + "\")").collect(Collectors.joining(",\n " + indent)) + "\n"
+ indent + " )));\n";
}
for (KeyConstraint key : entity.getKeys()) {
imports.add(List.class);
imports.add(KeyConstraint.class);
java += indent + "entity.addKey(new KeyConstraint(entity, List.of(\n"
+ indent + " " + key.getFields().stream().map(f -> "entity.getField(\"" + f.getName() + "\")").collect(Collectors.joining(",\n " + indent)) + "\n"
+ indent + " )));\n";
}
for (IndexConstraint index : entity.getIndexes()) {
imports.add(List.class);
imports.add(IndexConstraint.class);
java += indent + "entity.addIndex(new IndexConstraint(entity, List.of(\n"
+ indent + " " + index.getFields().stream().map(f -> "entity.getField(\"" + f.getName() + "\")").collect(Collectors.joining(",\n " + indent)) + "\n"
+ indent + " )));\n";
}
}
java += " }\n"
+ "}\n";
//imports
var normalImports = imports.stream().filter(e -> !e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
var javaImports = imports.stream().filter(e -> e.getName().startsWith("java")).sorted(Comparator.comparing(Class::getName, String.CASE_INSENSITIVE_ORDER)).toList();
//finalize
java = "package " + packageName + ";\n\n"
+ normalImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
+ javaImports.stream().map(e -> "import " + e.getName().replace("$", ".") + ";").collect(Collectors.joining("\n")) + "\n\n"
+ java;
return java;
}
}

View File

@@ -0,0 +1,116 @@
package jef.model.migration.creator;
import jef.model.ModelBuilder;
import jef.util.Check;
import lombok.Getter;
import java.util.Objects;
@Getter
public class ModelChangeDetector {
private final ModelBuilder from;
private final ModelBuilder to;
public ModelChangeDetector(ModelBuilder from, ModelBuilder to) {
this.from = Check.notNull(from, "from").clone();
this.to = Check.notNull(to, "to").clone();
}
public ModelChangeDetector detect() {
extractChanges();
return this;
}
private void extractChanges() {
extractChangesFromTo(from, to);
extractChangesFromTo(to, from);
for (int i = 0; i < to.getEntities().size(); i++) {
var toEntity = to.getEntities().get(i);
var fromEntity = from.getEntities().stream().filter(e -> e.getTypeName().equals(toEntity.getTypeName())).findFirst().orElse(null);
if (fromEntity != null) {
//entity empty
if (toEntity.getName().equals(fromEntity.getName())
&& toEntity.getFields().isEmpty()
&& toEntity.getPrimaryKey() == null
&& toEntity.getForeignKeys().isEmpty()
&& toEntity.getUniqueKeys().isEmpty()
&& toEntity.getKeys().isEmpty()
&& toEntity.getIndexes().isEmpty()
&& fromEntity.getFields().isEmpty()
&& fromEntity.getPrimaryKey() == null
&& fromEntity.getForeignKeys().isEmpty()
&& fromEntity.getUniqueKeys().isEmpty()
&& fromEntity.getKeys().isEmpty()
&& fromEntity.getIndexes().isEmpty()) {
to.dropEntity(toEntity);
from.dropEntity(fromEntity);
i--;
}
}
}
}
private void extractChangesFromTo(ModelBuilder from, ModelBuilder to) {
for (int i = 0; i < to.getEntities().size(); i++) {
var toEntity = to.getEntities().get(i);
var fromEntity = from.getEntities().stream().filter(e -> e.getTypeName().equals(toEntity.getTypeName())).findFirst().orElse(null);
if (fromEntity != null) {
for (int j = 0; j < toEntity.getFields().size(); j++) {
var toField = toEntity.getFields().get(j);
var fromField = fromEntity.getFields().stream().filter(e -> e.equals(toField)).findFirst().orElse(null);
if (fromField != null) {
toEntity.dropField(toField);
fromEntity.dropField(fromField);
j--;
}
}
if (Objects.equals(fromEntity.getPrimaryKey(), toEntity.getPrimaryKey())) {
fromEntity.setPrimaryKey(null);
toEntity.setPrimaryKey(null);
}
for (int j = 0; j < toEntity.getForeignKeys().size(); j++) {
var toFk = toEntity.getForeignKeys().get(j);
var fromFk = fromEntity.getForeignKeys().stream().filter(e -> e.equals(toFk)).findFirst().orElse(null);
if (fromFk != null) {
toEntity.dropForeignKey(toFk);
fromEntity.dropForeignKey(fromFk);
j--;
}
}
for (int j = 0; j < toEntity.getUniqueKeys().size(); j++) {
var toUnique = toEntity.getUniqueKeys().get(j);
var fromUnique = fromEntity.getUniqueKeys().stream().filter(e -> e.equals(toUnique)).findFirst().orElse(null);
if (fromUnique != null) {
toEntity.dropUniqueKey(toUnique);
fromEntity.dropUniqueKey(fromUnique);
j--;
}
}
for (int j = 0; j < toEntity.getKeys().size(); j++) {
var toKey = toEntity.getKeys().get(j);
var fromKey = fromEntity.getKeys().stream().filter(e -> e.equals(toKey)).findFirst().orElse(null);
if (fromKey != null) {
toEntity.dropKey(toKey);
fromEntity.dropKey(fromKey);
j--;
}
}
for (int j = 0; j < toEntity.getIndexes().size(); j++) {
var toIx = toEntity.getIndexes().get(j);
var fromIx = fromEntity.getIndexes().stream().filter(e -> e.equals(toIx)).findFirst().orElse(null);
if (fromIx != null) {
toEntity.dropIndex(toIx);
fromEntity.dropIndex(fromIx);
j--;
}
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
package jef.model.migration.operation;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class AddFieldOperation implements MigrationOperation {
protected final String table;
protected final String field;
protected final String sqlType;
protected final boolean notNull;
public static class Builder implements MigrationOperation.Builder<AddFieldOperation> {
protected final String table;
protected final String field;
protected String sqlType;
protected boolean notNull;
public Builder(String table, String field) {
this.table = table;
this.field = field;
}
public Builder sqlType(String sqlType) {
this.sqlType = sqlType;
return this;
}
public Builder notNull(boolean notNull) {
this.notNull = notNull;
return this;
}
public AddFieldOperation build() {
return new AddFieldOperation(table, field, sqlType, notNull);
}
}
}

View File

@@ -0,0 +1,44 @@
package jef.model.migration.operation;
import jef.model.constraints.ForeignKeyConstraint;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
public class AddForeignKeyOperation implements MigrationOperation {
private final String name;
private final String table;
private final List<String> fields;
private final String referencedTable;
private final List<String> referencedFields;
private final ForeignKeyConstraint.Action onUpdate;
private final ForeignKeyConstraint.Action onDelete;
public static class Builder implements MigrationOperation.Builder<AddForeignKeyOperation> {
private final String name;
private final String table;
private final List<String> fields;
private final String referencedTable;
private final List<String> referencedFields;
private final ForeignKeyConstraint.Action onUpdate;
private final ForeignKeyConstraint.Action onDelete;
public Builder(String name, String table, List<String> fields, String referencedTable, List<String> referencedFields, ForeignKeyConstraint.Action onUpdate, ForeignKeyConstraint.Action onDelete) {
this.name = name;
this.table = table;
this.fields = fields;
this.referencedTable = referencedTable;
this.referencedFields = referencedFields;
this.onUpdate = onUpdate;
this.onDelete = onDelete;
}
public AddForeignKeyOperation build() {
return new AddForeignKeyOperation(name, table, fields, referencedTable, referencedFields, onUpdate, onDelete);
}
}
}

View File

@@ -0,0 +1,23 @@
package jef.model.migration.operation;
import lombok.Getter;
import java.util.List;
import java.util.stream.Collectors;
@Getter
public class AddIndexOperation extends AddKeyOperationBase {
public AddIndexOperation(String name, String table, List<String> fields) {
super(name, table, fields);
}
public static class Builder extends AddKeyOperationBase.Builder<AddIndexOperation> {
public Builder(String name, String table, List<String> fields) {
super(name, table, fields);
}
public AddIndexOperation build() {
return new AddIndexOperation(name, table, fields);
}
}
}

View File

@@ -0,0 +1,23 @@
package jef.model.migration.operation;
import lombok.Getter;
import java.util.List;
import java.util.stream.Collectors;
@Getter
public class AddKeyOperation extends AddKeyOperationBase {
public AddKeyOperation(String name, String table, List<String> fields) {
super(name, table, fields);
}
public static class Builder extends AddKeyOperationBase.Builder<AddKeyOperation> {
public Builder(String name, String table, List<String> fields) {
super(name, table, fields);
}
public AddKeyOperation build() {
return new AddKeyOperation(name, table, fields);
}
}
}

View File

@@ -0,0 +1,26 @@
package jef.model.migration.operation;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public abstract class AddKeyOperationBase implements MigrationOperation {
protected final String name;
protected final String table;
protected final List<String> fields;
public abstract static class Builder<T extends AddKeyOperationBase> implements MigrationOperation.Builder<T> {
protected final String name;
protected final String table;
protected final List<String> fields;
public Builder(String name, String table, List<String> fields) {
this.name = name;
this.table = table;
this.fields = fields;
}
}
}

View File

@@ -0,0 +1,23 @@
package jef.model.migration.operation;
import lombok.Getter;
import java.util.List;
import java.util.stream.Collectors;
@Getter
public class AddPrimaryKeyOperation extends AddKeyOperationBase {
public AddPrimaryKeyOperation(String name, String table, List<String> fields) {
super(name, table, fields);
}
public static class Builder extends AddKeyOperationBase.Builder<AddPrimaryKeyOperation> {
public Builder(String name, String table, List<String> fields) {
super(name, table, fields);
}
public AddPrimaryKeyOperation build() {
return new AddPrimaryKeyOperation(name, table, fields);
}
}
}

View File

@@ -0,0 +1,28 @@
package jef.model.migration.operation;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
public class AddTableOperation implements MigrationOperation {
private final String table;
private final List<AddFieldOperation.Builder> fields;
public static class Builder implements MigrationOperation.Builder<AddTableOperation> {
private final String table;
private final List<AddFieldOperation.Builder> fields;
public Builder(String table, List<AddFieldOperation.Builder> fields) {
this.table = table;
this.fields = fields;
}
public AddTableOperation build() {
return new AddTableOperation(table, fields);
}
}
}

View File

@@ -0,0 +1,23 @@
package jef.model.migration.operation;
import lombok.Getter;
import java.util.List;
import java.util.stream.Collectors;
@Getter
public class AddUniqueKeyOperation extends AddKeyOperationBase {
public AddUniqueKeyOperation(String name, String table, List<String> fields) {
super(name, table, fields);
}
public static class Builder extends AddKeyOperationBase.Builder<AddUniqueKeyOperation> {
public Builder(String name, String table, List<String> fields) {
super(name, table, fields);
}
public AddUniqueKeyOperation build() {
return new AddUniqueKeyOperation(name, table, fields);
}
}
}

View File

@@ -0,0 +1,27 @@
package jef.model.migration.operation;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class DropConstraintOperation implements MigrationOperation {
private final String name;
private final String table;
public static class Builder implements MigrationOperation.Builder<DropConstraintOperation> {
private final String name;
private final String table;
public Builder(String name, String table) {
this.name = name;
this.table = table;
}
public DropConstraintOperation build() {
return new DropConstraintOperation(name, table);
}
}
}

View File

@@ -0,0 +1,27 @@
package jef.model.migration.operation;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class DropFieldOperation implements MigrationOperation {
private final String table;
private final String field;
public static class Builder implements MigrationOperation.Builder<DropFieldOperation> {
private final String table;
private final String field;
public Builder(String table, String field) {
this.table = table;
this.field = field;
}
public DropFieldOperation build() {
return new DropFieldOperation(table, field);
}
}
}

View File

@@ -0,0 +1,24 @@
package jef.model.migration.operation;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class DropTableOperation implements MigrationOperation {
private final String table;
public static class Builder implements MigrationOperation.Builder<DropTableOperation> {
private final String table;
public Builder(String table) {
this.table = table;
}
public DropTableOperation build() {
return new DropTableOperation(table);
}
}
}

View File

@@ -0,0 +1,7 @@
package jef.model.migration.operation;
public interface MigrationOperation {
public interface Builder<T extends MigrationOperation> {
T build();
}
}

View File

@@ -0,0 +1,30 @@
package jef.model.migration.operation;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class RenameFieldOperation implements MigrationOperation {
private final String table;
private final String oldName;
private final String newName;
public static class Builder implements MigrationOperation.Builder<RenameFieldOperation> {
private final String table;
private final String oldName;
private final String newName;
public Builder(String table, String oldName, String newName) {
this.table = table;
this.oldName = oldName;
this.newName = newName;
}
public RenameFieldOperation build() {
return new RenameFieldOperation(table, oldName, newName);
}
}
}

View File

@@ -0,0 +1,27 @@
package jef.model.migration.operation;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class RenameTableOperation implements MigrationOperation {
private final String oldName;
private final String newName;
public static class Builder implements MigrationOperation.Builder<RenameTableOperation> {
private final String oldName;
private final String newName;
public Builder(String oldName, String newName) {
this.oldName = oldName;
this.newName = newName;
}
public RenameTableOperation build() {
return new RenameTableOperation(oldName, newName);
}
}
}

View File

@@ -0,0 +1,32 @@
package jef.model.migration.operation;
import lombok.Getter;
import java.util.List;
@Getter
public class UpdateFieldOperation extends AddFieldOperation {
private final String newName;
public UpdateFieldOperation(String table, String field, String newName, String sqlType, boolean notNull) {
super(table, field, sqlType, notNull);
this.newName = newName;
}
public static class Builder extends AddFieldOperation.Builder {
private String newName;
public Builder(String table, String field) {
super(table, field);
}
public Builder newName(String newName) {
this.newName = newName;
return this;
}
public UpdateFieldOperation build() {
return new UpdateFieldOperation(table, field, newName, sqlType, notNull);
}
}
}

View File

@@ -0,0 +1,10 @@
package jef.util;
public abstract class Check {
public static <T> T notNull(T t, String name) {
if (t == null) {
throw new IllegalArgumentException(name + " must be not null");
}
return t;
}
}

View File

@@ -0,0 +1,18 @@
package jef.util;
import java.util.Optional;
public abstract class Util {
public static <T> Optional<T> tryGet(ThrowableSupplier<T> s) {
try {
return Optional.ofNullable(s.get());
} catch (Throwable t) {
return Optional.empty();
}
}
@FunctionalInterface
public interface ThrowableSupplier<T> {
T get() throws Throwable;
}
}