diff --git a/src/main/java/jef/model/DbContext.java b/src/main/java/jef/model/DbContext.java index 530f6b9..582a26f 100644 --- a/src/main/java/jef/model/DbContext.java +++ b/src/main/java/jef/model/DbContext.java @@ -1,4 +1,6 @@ package jef.model; public abstract class DbContext { + public void onModelCreate(ModelBuilder mb) { + } } diff --git a/src/main/java/jef/model/DbEntity.java b/src/main/java/jef/model/DbEntity.java index 56b442c..3976f10 100644 --- a/src/main/java/jef/model/DbEntity.java +++ b/src/main/java/jef/model/DbEntity.java @@ -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 { - private final Class type; + private final String typeName; + @Setter(value = AccessLevel.PACKAGE) + private Class type; private final List> fields; private String name; private PrimaryKeyConstraint primaryKey; private final List foreignKeys = new ArrayList<>(); - private final List uniqueKeys = new ArrayList<>(); + private final List uniqueKeys = new ArrayList<>(); private final List keys = new ArrayList<>(); private final List indexes = new ArrayList<>(); - DbEntity(Class 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 type, List> fields) { - this.type = type; - this.fields = fields; - this.name = type.getSimpleName(); + DbEntity(Class type) { + this(type, type.getSimpleName()); + } + + DbEntity(Class type, String name) { + this(type, name, new ArrayList<>()); + } + + DbEntity(Class type, String name, List> 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> getFields() { return Collections.unmodifiableList(fields); } + public List getForeignKeys() { + return Collections.unmodifiableList(foreignKeys); + } + + public List getUniqueKeys() { + return Collections.unmodifiableList(uniqueKeys); + } + + public List getKeys() { + return Collections.unmodifiableList(keys); + } + + public List 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 { return (DbField) fields.stream().filter(e -> e.getField().equals(field)).findFirst().orElse(null); } + public DbField getField(String name) { + return (DbField) fields.stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null); + } + public DbField getOrCreateField(SerializableFunction getter) { try { var prop = getField(getter); @@ -93,6 +133,19 @@ public class DbEntity { } } + public DbField getOrCreateField(String name, String typeName) { + try { + var prop = (DbField) 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 DbField addIfAbsent(DbField field) { try { var prop = (DbField) fields.stream().filter(e -> e.getName().equals(field.getName())).findFirst().orElse(null); @@ -106,6 +159,26 @@ public class DbEntity { } } + 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 String extractFieldName(SerializableFunction getter) { try { var expr = new AsmParser(getter).parse(); @@ -125,15 +198,9 @@ public class DbEntity { } } - 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 { } } + 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 + '\'' + + '}'; } } diff --git a/src/main/java/jef/model/DbField.java b/src/main/java/jef/model/DbField.java index 4b58eac..3dc2244 100644 --- a/src/main/java/jef/model/DbField.java +++ b/src/main/java/jef/model/DbField.java @@ -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 { private final DbEntity entity; - private final Class type; - private final Field field; + private final String typeName; + @Setter(value = AccessLevel.PACKAGE) + private Class 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 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 entity, Class type, Field field) { this(entity, type, field, field.getName()); } DbField(DbEntity entity, Class 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 { 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 { 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 { 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 setModelField(boolean modelField) { + isModelField = modelField; + return this; + } + + public DbField setDatabaseField(boolean databaseField) { + isDatabaseField = databaseField; + return this; + } + + public DbField 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 + + '}'; } } diff --git a/src/main/java/jef/model/EntityInitializer.java b/src/main/java/jef/model/EntityInitializer.java index 97c471e..8179336 100644 --- a/src/main/java/jef/model/EntityInitializer.java +++ b/src/main/java/jef/model/EntityInitializer.java @@ -19,12 +19,13 @@ class EntityInitializer { throw new ModelException("DbSet " + ctxfield.getName() + " is missing the " + Clazz.class.getSimpleName() + " annotation"); } var dbsetClazz = (Class) clazzAnnotation.clazz(); - initEntity(mb, dbsetClazz); + initEntity(mb, dbsetClazz, ctxfield.getName()); } } - static void initEntity(ModelBuilder mb, Class clazz) { + static void initEntity(ModelBuilder mb, Class clazz, String name) { var entity = mb.getOrCreateEntity(clazz); + entity.setName(name); var fields = ReflectionUtil.getFieldsRecursive(clazz); for (var f : fields) { diff --git a/src/main/java/jef/model/ForeignKeyInitializer.java b/src/main/java/jef/model/ForeignKeyInitializer.java index 00a4376..8c4b204 100644 --- a/src/main/java/jef/model/ForeignKeyInitializer.java +++ b/src/main/java/jef/model/ForeignKeyInitializer.java @@ -33,7 +33,7 @@ class ForeignKeyInitializer { } var otherEntity = mb.getEntity((Class) clazzAnnotation.clazz()); if (otherEntity == null) { - EntityInitializer.initEntity(mb, (Class) clazzAnnotation.clazz()); + EntityInitializer.initEntity(mb, (Class) clazzAnnotation.clazz(), f.getName()); otherEntity = mb.getEntity((Class) clazzAnnotation.clazz()); PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity); } @@ -88,7 +88,7 @@ class ForeignKeyInitializer { } else if (SerializableObject.class.isAssignableFrom(f.getType())) { var otherEntity = mb.getEntity((Class) f.getType()); if (otherEntity == null) { - EntityInitializer.initEntity(mb, (Class) f.getType()); + EntityInitializer.initEntity(mb, (Class) f.getType(), f.getName()); otherEntity = mb.getEntity((Class) f.getType()); PrimaryKeyInitializer.initPrimaryKeys(mb, otherEntity); } diff --git a/src/main/java/jef/model/ModelBuilder.java b/src/main/java/jef/model/ModelBuilder.java index 98d77f5..a35d711 100644 --- a/src/main/java/jef/model/ModelBuilder.java +++ b/src/main/java/jef/model/ModelBuilder.java @@ -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 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 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 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> entities; - public ModelBuilder(List> entities) { - this.entities = entities; + /** + * Initializes an empty ModelBuilder + */ + public ModelBuilder() { + this(List.of()); } + /** + * Initializes a new ModelBuilder with the provided entities. + */ + public ModelBuilder(List> entities) { + this.entities = new ArrayList<>(entities); + } + + /** + * Returns an unmodifiable List of all entities. + * + * @return an unmodifiable List of all entities + */ public List> 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 DbEntity getEntity(Class clazz) { - return (DbEntity) 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 the type of model class + * @return the database model for the requested class or null if not present. + */ + public DbEntity getEntity(String typeName) { + Check.notNull(typeName, "typeName"); + return (DbEntity) 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 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 DbEntity getOrCreateEntity(Class 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 the type of model class + * @return the DbEntity for the requested class or the newly created empty DbEntity if none existed. + */ + public DbEntity getOrCreateEntity(String typeName) { + Check.notNull(typeName, "typeName"); + var entity = (DbEntity) 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 the type of the entity + * @return whether the entity was present in this ModelBuilder + */ + public boolean dropEntity(DbEntity entity) { + Check.notNull(entity, "entity"); + var removed = this.entities.remove(entity); + if (!removed) { + return false; + } + for (DbEntity 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(); + 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); + } } diff --git a/src/main/java/jef/model/annotations/processors/UniqueProcessor.java b/src/main/java/jef/model/annotations/processors/UniqueProcessor.java index cb76b93..20a4bf4 100644 --- a/src/main/java/jef/model/annotations/processors/UniqueProcessor.java +++ b/src/main/java/jef/model/annotations/processors/UniqueProcessor.java @@ -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 { +public class UniqueProcessor extends KeyProcessorBase { public static final UniqueProcessor INSTANCE = new UniqueProcessor(); private UniqueProcessor() { @@ -17,13 +17,13 @@ public class UniqueProcessor extends KeyProcessorBase } @Override - protected UniqueConstraint initConstraint(DbEntity entity, List> fields) { - return new UniqueConstraint(entity, fields); + protected UniqueKeyConstraint initConstraint(DbEntity entity, List> 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 diff --git a/src/main/java/jef/model/constraints/ConstraintBase.java b/src/main/java/jef/model/constraints/ConstraintBase.java new file mode 100644 index 0000000..2789041 --- /dev/null +++ b/src/main/java/jef/model/constraints/ConstraintBase.java @@ -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> fields; + + public ConstraintBase(DbEntity entity, List> 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()); + } +} diff --git a/src/main/java/jef/model/constraints/ForeignKeyConstraint.java b/src/main/java/jef/model/constraints/ForeignKeyConstraint.java index 384b2c3..3b38922 100644 --- a/src/main/java/jef/model/constraints/ForeignKeyConstraint.java +++ b/src/main/java/jef/model/constraints/ForeignKeyConstraint.java @@ -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> fields; +public class ForeignKeyConstraint extends ConstraintBase { private final DbEntity referencedEntity; private final List> referencedFields; private final Action onUpdate; private final Action onDelete; + public ForeignKeyConstraint(DbEntity entity, List> fields, DbEntity referencedEntity, List> 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, + ; } } diff --git a/src/main/java/jef/model/constraints/IndexConstraint.java b/src/main/java/jef/model/constraints/IndexConstraint.java index 8c9c25f..6e28877 100644 --- a/src/main/java/jef/model/constraints/IndexConstraint.java +++ b/src/main/java/jef/model/constraints/IndexConstraint.java @@ -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> fields; +@EqualsAndHashCode(callSuper = true) +public class IndexConstraint extends ConstraintBase { + public IndexConstraint(DbEntity entity, List> fields) { + super(entity, fields); + } @Override public String getName() { diff --git a/src/main/java/jef/model/constraints/KeyConstraint.java b/src/main/java/jef/model/constraints/KeyConstraint.java index 5be25c9..59b969a 100644 --- a/src/main/java/jef/model/constraints/KeyConstraint.java +++ b/src/main/java/jef/model/constraints/KeyConstraint.java @@ -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> fields; +@EqualsAndHashCode(callSuper = true) +public class KeyConstraint extends ConstraintBase { + public KeyConstraint(DbEntity entity, List> fields) { + super(entity, fields); + } @Override public String getName() { diff --git a/src/main/java/jef/model/constraints/PrimaryKeyConstraint.java b/src/main/java/jef/model/constraints/PrimaryKeyConstraint.java index a17f42a..d7f98ec 100644 --- a/src/main/java/jef/model/constraints/PrimaryKeyConstraint.java +++ b/src/main/java/jef/model/constraints/PrimaryKeyConstraint.java @@ -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> fields; +@EqualsAndHashCode(callSuper = true) +public class PrimaryKeyConstraint extends ConstraintBase { + public PrimaryKeyConstraint(DbEntity entity, List> fields) { + super(entity, fields); + } @Override public String getName() { diff --git a/src/main/java/jef/model/constraints/UniqueConstraint.java b/src/main/java/jef/model/constraints/UniqueKeyConstraint.java similarity index 73% rename from src/main/java/jef/model/constraints/UniqueConstraint.java rename to src/main/java/jef/model/constraints/UniqueKeyConstraint.java index ce5f133..8bf2461 100644 --- a/src/main/java/jef/model/constraints/UniqueConstraint.java +++ b/src/main/java/jef/model/constraints/UniqueKeyConstraint.java @@ -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> fields; +@EqualsAndHashCode(callSuper = true) +public class UniqueKeyConstraint extends ConstraintBase { + public UniqueKeyConstraint(DbEntity entity, List> fields) { + super(entity, fields); + } @Override public String getName() { diff --git a/src/main/java/jef/model/migration/Migration.java b/src/main/java/jef/model/migration/Migration.java new file mode 100644 index 0000000..323e071 --- /dev/null +++ b/src/main/java/jef/model/migration/Migration.java @@ -0,0 +1,7 @@ +package jef.model.migration; + +public interface Migration { + void up(MigrationBuilder migrationBuilder); + + void down(MigrationBuilder migrationBuilder); +} diff --git a/src/main/java/jef/model/migration/MigrationBuilder.java b/src/main/java/jef/model/migration/MigrationBuilder.java new file mode 100644 index 0000000..f098e92 --- /dev/null +++ b/src/main/java/jef/model/migration/MigrationBuilder.java @@ -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> operations = new ArrayList<>(); + + public AddTableOperation.Builder addTable(String table, List 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 fields) { + var op = new AddPrimaryKeyOperation.Builder(name, table, fields); + operations.add(op); + return op; + } + + public AddForeignKeyOperation.Builder addForeignKey(String name, String table, List fields, String referencedTable, List 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 fields) { + var op = new AddUniqueKeyOperation.Builder(name, table, fields); + operations.add(op); + return op; + } + + public AddKeyOperation.Builder addKey(String name, String table, List fields) { + var op = new AddKeyOperation.Builder(name, table, fields); + operations.add(op); + return op; + } + + public AddIndexOperation.Builder addIndex(String name, String table, List 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; + } +} diff --git a/src/main/java/jef/model/migration/creator/MigrationBuilderGenerator.java b/src/main/java/jef/model/migration/creator/MigrationBuilderGenerator.java new file mode 100644 index 0000000..bb040fa --- /dev/null +++ b/src/main/java/jef/model/migration/creator/MigrationBuilderGenerator.java @@ -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 opsUp; + private final List opsDown; + private final String name; + private final String packageName; + + private final Set> 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, Function> OP_TO_STRING_MAPPERS = initMapper(); + private final Function UNSUPPORTED_MIGRATION_OPERATION_FUNCTION = (MigrationOperation i) -> { + throw new RuntimeException(new UnsupportedOperationException("Unsupported migration operation: " + i.getClass().getSimpleName())); + }; + + private Map, Function> initMapper() { + Map, Function> 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) + 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() + ");"; + } +} diff --git a/src/main/java/jef/model/migration/creator/MigrationCreator.java b/src/main/java/jef/model/migration/creator/MigrationCreator.java new file mode 100644 index 0000000..48c1058 --- /dev/null +++ b/src/main/java/jef/model/migration/creator/MigrationCreator.java @@ -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(); + var migrationDownOps = new ArrayList(); + + 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 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 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 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 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 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>(); + var handledTo = new ArrayList>(); + + //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(); + 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 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 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 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 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 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 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 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 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 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 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 stepsUp = new ArrayList<>(); + private List 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; + } + } +} diff --git a/src/main/java/jef/model/migration/creator/ModelBuilderGenerator.java b/src/main/java/jef/model/migration/creator/ModelBuilderGenerator.java new file mode 100644 index 0000000..342e0e6 --- /dev/null +++ b/src/main/java/jef/model/migration/creator/ModelBuilderGenerator.java @@ -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> 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 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 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; + } +} diff --git a/src/main/java/jef/model/migration/creator/ModelChangeDetector.java b/src/main/java/jef/model/migration/creator/ModelChangeDetector.java new file mode 100644 index 0000000..669cdb1 --- /dev/null +++ b/src/main/java/jef/model/migration/creator/ModelChangeDetector.java @@ -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--; + } + } + } + } + } +} diff --git a/src/main/java/jef/model/migration/operation/AddFieldOperation.java b/src/main/java/jef/model/migration/operation/AddFieldOperation.java new file mode 100644 index 0000000..f1caca2 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/AddFieldOperation.java @@ -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 { + 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); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/AddForeignKeyOperation.java b/src/main/java/jef/model/migration/operation/AddForeignKeyOperation.java new file mode 100644 index 0000000..382248f --- /dev/null +++ b/src/main/java/jef/model/migration/operation/AddForeignKeyOperation.java @@ -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 fields; + private final String referencedTable; + private final List referencedFields; + private final ForeignKeyConstraint.Action onUpdate; + private final ForeignKeyConstraint.Action onDelete; + + public static class Builder implements MigrationOperation.Builder { + private final String name; + private final String table; + private final List fields; + private final String referencedTable; + private final List referencedFields; + private final ForeignKeyConstraint.Action onUpdate; + private final ForeignKeyConstraint.Action onDelete; + + public Builder(String name, String table, List fields, String referencedTable, List 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); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/AddIndexOperation.java b/src/main/java/jef/model/migration/operation/AddIndexOperation.java new file mode 100644 index 0000000..9ae1c1b --- /dev/null +++ b/src/main/java/jef/model/migration/operation/AddIndexOperation.java @@ -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 fields) { + super(name, table, fields); + } + + public static class Builder extends AddKeyOperationBase.Builder { + public Builder(String name, String table, List fields) { + super(name, table, fields); + } + + public AddIndexOperation build() { + return new AddIndexOperation(name, table, fields); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/AddKeyOperation.java b/src/main/java/jef/model/migration/operation/AddKeyOperation.java new file mode 100644 index 0000000..870df5a --- /dev/null +++ b/src/main/java/jef/model/migration/operation/AddKeyOperation.java @@ -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 fields) { + super(name, table, fields); + } + + public static class Builder extends AddKeyOperationBase.Builder { + public Builder(String name, String table, List fields) { + super(name, table, fields); + } + + public AddKeyOperation build() { + return new AddKeyOperation(name, table, fields); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/AddKeyOperationBase.java b/src/main/java/jef/model/migration/operation/AddKeyOperationBase.java new file mode 100644 index 0000000..80708e2 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/AddKeyOperationBase.java @@ -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 fields; + + public abstract static class Builder implements MigrationOperation.Builder { + protected final String name; + protected final String table; + protected final List fields; + + public Builder(String name, String table, List fields) { + this.name = name; + this.table = table; + this.fields = fields; + } + } +} diff --git a/src/main/java/jef/model/migration/operation/AddPrimaryKeyOperation.java b/src/main/java/jef/model/migration/operation/AddPrimaryKeyOperation.java new file mode 100644 index 0000000..cd7b964 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/AddPrimaryKeyOperation.java @@ -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 fields) { + super(name, table, fields); + } + + public static class Builder extends AddKeyOperationBase.Builder { + public Builder(String name, String table, List fields) { + super(name, table, fields); + } + + public AddPrimaryKeyOperation build() { + return new AddPrimaryKeyOperation(name, table, fields); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/AddTableOperation.java b/src/main/java/jef/model/migration/operation/AddTableOperation.java new file mode 100644 index 0000000..792c3ff --- /dev/null +++ b/src/main/java/jef/model/migration/operation/AddTableOperation.java @@ -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 fields; + + public static class Builder implements MigrationOperation.Builder { + private final String table; + private final List fields; + + public Builder(String table, List fields) { + this.table = table; + this.fields = fields; + } + + public AddTableOperation build() { + return new AddTableOperation(table, fields); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/AddUniqueKeyOperation.java b/src/main/java/jef/model/migration/operation/AddUniqueKeyOperation.java new file mode 100644 index 0000000..7840cb4 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/AddUniqueKeyOperation.java @@ -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 fields) { + super(name, table, fields); + } + + public static class Builder extends AddKeyOperationBase.Builder { + public Builder(String name, String table, List fields) { + super(name, table, fields); + } + + public AddUniqueKeyOperation build() { + return new AddUniqueKeyOperation(name, table, fields); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/DropConstraintOperation.java b/src/main/java/jef/model/migration/operation/DropConstraintOperation.java new file mode 100644 index 0000000..c5e50b8 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/DropConstraintOperation.java @@ -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 { + 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); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/DropFieldOperation.java b/src/main/java/jef/model/migration/operation/DropFieldOperation.java new file mode 100644 index 0000000..2a5a875 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/DropFieldOperation.java @@ -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 { + 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); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/DropTableOperation.java b/src/main/java/jef/model/migration/operation/DropTableOperation.java new file mode 100644 index 0000000..8e0a014 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/DropTableOperation.java @@ -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 { + private final String table; + + public Builder(String table) { + this.table = table; + } + + public DropTableOperation build() { + return new DropTableOperation(table); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/MigrationOperation.java b/src/main/java/jef/model/migration/operation/MigrationOperation.java new file mode 100644 index 0000000..0bf1faf --- /dev/null +++ b/src/main/java/jef/model/migration/operation/MigrationOperation.java @@ -0,0 +1,7 @@ +package jef.model.migration.operation; + +public interface MigrationOperation { + public interface Builder { + T build(); + } +} diff --git a/src/main/java/jef/model/migration/operation/RenameFieldOperation.java b/src/main/java/jef/model/migration/operation/RenameFieldOperation.java new file mode 100644 index 0000000..20e93c9 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/RenameFieldOperation.java @@ -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 { + 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); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/RenameTableOperation.java b/src/main/java/jef/model/migration/operation/RenameTableOperation.java new file mode 100644 index 0000000..97849b4 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/RenameTableOperation.java @@ -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 { + 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); + } + } +} diff --git a/src/main/java/jef/model/migration/operation/UpdateFieldOperation.java b/src/main/java/jef/model/migration/operation/UpdateFieldOperation.java new file mode 100644 index 0000000..f5994c9 --- /dev/null +++ b/src/main/java/jef/model/migration/operation/UpdateFieldOperation.java @@ -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); + } + } +} diff --git a/src/main/java/jef/util/Check.java b/src/main/java/jef/util/Check.java new file mode 100644 index 0000000..037127b --- /dev/null +++ b/src/main/java/jef/util/Check.java @@ -0,0 +1,10 @@ +package jef.util; + +public abstract class Check { + public static T notNull(T t, String name) { + if (t == null) { + throw new IllegalArgumentException(name + " must be not null"); + } + return t; + } +} diff --git a/src/main/java/jef/util/Util.java b/src/main/java/jef/util/Util.java new file mode 100644 index 0000000..9938e26 --- /dev/null +++ b/src/main/java/jef/util/Util.java @@ -0,0 +1,18 @@ +package jef.util; + +import java.util.Optional; + +public abstract class Util { + public static Optional tryGet(ThrowableSupplier s) { + try { + return Optional.ofNullable(s.get()); + } catch (Throwable t) { + return Optional.empty(); + } + } + + @FunctionalInterface + public interface ThrowableSupplier { + T get() throws Throwable; + } +} diff --git a/src/test/java/jef/model/DbContextSimpleTest.java b/src/test/java/jef/model/DbContextSimpleTest.java index c701f22..7c14fa7 100644 --- a/src/test/java/jef/model/DbContextSimpleTest.java +++ b/src/test/java/jef/model/DbContextSimpleTest.java @@ -16,7 +16,7 @@ class DbContextSimpleTest { public void test() { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertEquals(5, mb.getEntity(TestClass.class).getFields().size()); assertEquals(1, mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i")).count()); diff --git a/src/test/java/jef/model/EntityInitializerMultiple1To1RelationTest.java b/src/test/java/jef/model/EntityInitializerMultiple1To1RelationTest.java index effca3e..f0d9e9f 100644 --- a/src/test/java/jef/model/EntityInitializerMultiple1To1RelationTest.java +++ b/src/test/java/jef/model/EntityInitializerMultiple1To1RelationTest.java @@ -16,8 +16,8 @@ class EntityInitializerMultiple1To1RelationTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(2, mb.getEntities().size()); - assertEquals(TestClass2.class.getSimpleName(), mb.getEntity(TestClass2.class).getName()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects2", mb.getEntity(TestClass2.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(TestClass2.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); diff --git a/src/test/java/jef/model/ForeignKeyInitializerNToNRelationTest.java b/src/test/java/jef/model/ForeignKeyInitializerNToNRelationTest.java index f2c843e..2f61a0b 100644 --- a/src/test/java/jef/model/ForeignKeyInitializerNToNRelationTest.java +++ b/src/test/java/jef/model/ForeignKeyInitializerNToNRelationTest.java @@ -18,9 +18,9 @@ class ForeignKeyInitializerNToNRelationTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(3, mb.getEntities().size()); - assertEquals(TestClass2.class.getSimpleName(), mb.getEntity(TestClass2.class).getName()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); - assertEquals(Mapping.class.getSimpleName(), mb.getEntity(Mapping.class).getName()); + assertEquals("objects2", mb.getEntity(TestClass2.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); + assertEquals("rels", mb.getEntity(Mapping.class).getName()); assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(Mapping.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(Mapping.class))); diff --git a/src/test/java/jef/model/ForeignKeyInitializerNestedList2LayerTest.java b/src/test/java/jef/model/ForeignKeyInitializerNestedList2LayerTest.java index 11e6ccc..41ca7d8 100644 --- a/src/test/java/jef/model/ForeignKeyInitializerNestedList2LayerTest.java +++ b/src/test/java/jef/model/ForeignKeyInitializerNestedList2LayerTest.java @@ -18,9 +18,9 @@ class ForeignKeyInitializerNestedList2LayerTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(3, mb.getEntities().size()); - assertEquals(TestClass3.class.getSimpleName(), mb.getEntity(TestClass3.class).getName()); - assertEquals(TestClass2.class.getSimpleName(), mb.getEntity(TestClass2.class).getName()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass3.class).getName()); + assertEquals("nested2", mb.getEntity(TestClass2.class).getName()); + assertEquals("nested", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass3.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass3.class))); assertTrue(mb.getEntity(TestClass2.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); diff --git a/src/test/java/jef/model/ForeignKeyInitializerNestedListSimpleTest.java b/src/test/java/jef/model/ForeignKeyInitializerNestedListSimpleTest.java index 62f0070..dd6ea7a 100644 --- a/src/test/java/jef/model/ForeignKeyInitializerNestedListSimpleTest.java +++ b/src/test/java/jef/model/ForeignKeyInitializerNestedListSimpleTest.java @@ -18,8 +18,8 @@ class ForeignKeyInitializerNestedListSimpleTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(2, mb.getEntities().size()); - assertEquals(TestClass2.class.getSimpleName(), mb.getEntity(TestClass2.class).getName()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass2.class).getName()); + assertEquals("nested", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(TestClass2.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); diff --git a/src/test/java/jef/model/ForeignKeyInitializerNestedObject2LayerTest.java b/src/test/java/jef/model/ForeignKeyInitializerNestedObject2LayerTest.java index 13ddaae..77fcead 100644 --- a/src/test/java/jef/model/ForeignKeyInitializerNestedObject2LayerTest.java +++ b/src/test/java/jef/model/ForeignKeyInitializerNestedObject2LayerTest.java @@ -16,9 +16,9 @@ class ForeignKeyInitializerNestedObject2LayerTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(3, mb.getEntities().size()); - assertEquals(TestClass3.class.getSimpleName(), mb.getEntity(TestClass3.class).getName()); - assertEquals(TestClass2.class.getSimpleName(), mb.getEntity(TestClass2.class).getName()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass3.class).getName()); + assertEquals("nested2", mb.getEntity(TestClass2.class).getName()); + assertEquals("nested", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass3.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass3.class))); assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); @@ -57,14 +57,14 @@ class ForeignKeyInitializerNestedObject2LayerTest { assertEquals(mb.getEntity(TestClass3.class), mb.getEntity(TestClass3.class).getForeignKeys().get(0).getEntity()); assertEquals(mb.getEntity(TestClass2.class), mb.getEntity(TestClass3.class).getForeignKeys().get(0).getReferencedEntity()); assertEquals(mb.getEntity(TestClass3.class).getFields().stream().filter(e -> e.getName().equals("nested2I")).toList(), - mb.getEntity(TestClass3.class).getForeignKeys().get(0).getFields()); + mb.getEntity(TestClass3.class).getForeignKeys().get(0).getFields()); assertEquals(mb.getEntity(TestClass2.class).getPrimaryKey().getFields(), mb.getEntity(TestClass3.class).getForeignKeys().get(0).getReferencedFields()); //refs TestClass2, TestClass assertEquals(mb.getEntity(TestClass2.class), mb.getEntity(TestClass2.class).getForeignKeys().get(0).getEntity()); assertEquals(mb.getEntity(TestClass.class), mb.getEntity(TestClass2.class).getForeignKeys().get(0).getReferencedEntity()); assertEquals(mb.getEntity(TestClass2.class).getFields().stream().filter(e -> e.getName().equals("nestedI")).toList(), - mb.getEntity(TestClass2.class).getForeignKeys().get(0).getFields()); + mb.getEntity(TestClass2.class).getForeignKeys().get(0).getFields()); assertEquals(mb.getEntity(TestClass.class).getPrimaryKey().getFields(), mb.getEntity(TestClass2.class).getForeignKeys().get(0).getReferencedFields()); // /keys ------------------------ } diff --git a/src/test/java/jef/model/ForeignKeyInitializerNestedObjectSimpleTest.java b/src/test/java/jef/model/ForeignKeyInitializerNestedObjectSimpleTest.java index 942ec76..a7f8b51 100644 --- a/src/test/java/jef/model/ForeignKeyInitializerNestedObjectSimpleTest.java +++ b/src/test/java/jef/model/ForeignKeyInitializerNestedObjectSimpleTest.java @@ -16,8 +16,8 @@ class ForeignKeyInitializerNestedObjectSimpleTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(2, mb.getEntities().size()); - assertEquals(TestClass2.class.getSimpleName(), mb.getEntity(TestClass2.class).getName()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass2.class).getName()); + assertEquals("nested", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(TestClass2.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); diff --git a/src/test/java/jef/model/ForeignKeyInitializerRecursive1To1RelationTest.java b/src/test/java/jef/model/ForeignKeyInitializerRecursive1To1RelationTest.java index 61dc097..af9a57c 100644 --- a/src/test/java/jef/model/ForeignKeyInitializerRecursive1To1RelationTest.java +++ b/src/test/java/jef/model/ForeignKeyInitializerRecursive1To1RelationTest.java @@ -16,7 +16,7 @@ class ForeignKeyInitializerRecursive1To1RelationTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects2", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); diff --git a/src/test/java/jef/model/ForeignKeyInitializerRecursive1ToNRelationTest.java b/src/test/java/jef/model/ForeignKeyInitializerRecursive1ToNRelationTest.java index 9ff027b..51b500a 100644 --- a/src/test/java/jef/model/ForeignKeyInitializerRecursive1ToNRelationTest.java +++ b/src/test/java/jef/model/ForeignKeyInitializerRecursive1ToNRelationTest.java @@ -18,7 +18,7 @@ class ForeignKeyInitializerRecursive1ToNRelationTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects2", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); diff --git a/src/test/java/jef/model/ForeignKeyInitializerRecursiveWithParent1ToNRelationTest.java b/src/test/java/jef/model/ForeignKeyInitializerRecursiveWithParent1ToNRelationTest.java index c89df11..c2a8f23 100644 --- a/src/test/java/jef/model/ForeignKeyInitializerRecursiveWithParent1ToNRelationTest.java +++ b/src/test/java/jef/model/ForeignKeyInitializerRecursiveWithParent1ToNRelationTest.java @@ -18,7 +18,7 @@ class ForeignKeyInitializerRecursiveWithParent1ToNRelationTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects2", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); diff --git a/src/test/java/jef/model/ModelBuilderCloneTest.java b/src/test/java/jef/model/ModelBuilderCloneTest.java new file mode 100644 index 0000000..ccb8dbf --- /dev/null +++ b/src/test/java/jef/model/ModelBuilderCloneTest.java @@ -0,0 +1,258 @@ +package jef.model; + +import jef.DbSet; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.annotations.Index; +import jef.model.annotations.Key; +import jef.model.annotations.Unique; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ModelBuilderCloneTest { + @Test + public void testClone() { + var mb = ModelBuilder.from(Ctx.class); + var copy = mb.clone(); + + debugHelper(mb, copy); + + //true check + assertEquals(mb, copy); + + //proper clone reference checks + for (int i = 0; i < mb.getEntities().size(); i++) { + assertTrue(mb.getEntities().get(i) != copy.getEntities().get(i)); + for (int j = 0; j < mb.getEntities().get(0).getFields().size(); j++) { + assertTrue(mb.getEntities().get(i).getFields().get(j) != copy.getEntities().get(i).getFields().get(j)); + assertTrue(mb.getEntities().get(i).getFields().get(j).getEntity() != copy.getEntities().get(i).getFields().get(j).getEntity()); + } + + assertTrue(mb.getEntities().get(i).getPrimaryKey() != copy.getEntities().get(i).getPrimaryKey()); + assertTrue(mb.getEntities().get(i).getPrimaryKey().getEntity() != copy.getEntities().get(i).getPrimaryKey().getEntity()); + + for (int j = 0; j < mb.getEntities().get(i).getPrimaryKey().getFields().size(); j++) { + assertTrue(mb.getEntities().get(i).getPrimaryKey().getFields().get(j) != copy.getEntities().get(i).getPrimaryKey().getFields().get(j)); + assertTrue(mb.getEntities().get(i).getPrimaryKey().getFields().get(j).getEntity() != copy.getEntities().get(i).getPrimaryKey().getFields().get(j).getEntity()); + } + + for (int j = 0; j < mb.getEntities().get(i).getForeignKeys().size(); j++) { + assertTrue(mb.getEntities().get(i).getForeignKeys().get(j) != copy.getEntities().get(i).getForeignKeys().get(j)); + assertTrue(mb.getEntities().get(i).getForeignKeys().get(j).getEntity() != copy.getEntities().get(i).getForeignKeys().get(j).getEntity()); + assertTrue(mb.getEntities().get(i).getForeignKeys().get(j).getReferencedEntity() != copy.getEntities().get(i).getForeignKeys().get(j).getReferencedEntity()); + + for (int k = 0; k < mb.getEntities().get(i).getForeignKeys().get(j).getFields().size(); k++) { + assertTrue(mb.getEntities().get(i).getForeignKeys().get(j).getFields().get(k) != copy.getEntities().get(i).getForeignKeys().get(j).getFields().get(k)); + assertTrue(mb.getEntities().get(i).getForeignKeys().get(j).getFields().get(k).getEntity() != copy.getEntities().get(i).getForeignKeys().get(j).getFields().get(k).getEntity()); + } + + for (int k = 0; k < mb.getEntities().get(i).getForeignKeys().get(j).getReferencedFields().size(); k++) { + assertTrue(mb.getEntities().get(i).getForeignKeys().get(j).getReferencedFields().get(k) != copy.getEntities().get(i).getForeignKeys().get(j).getReferencedFields().get(k)); + assertTrue(mb.getEntities().get(i).getForeignKeys().get(j).getReferencedFields().get(k).getEntity() != copy.getEntities().get(i).getForeignKeys().get(j).getReferencedFields().get(k).getEntity()); + } + } + + for (int j = 0; j < mb.getEntities().get(i).getUniqueKeys().size(); j++) { + assertTrue(mb.getEntities().get(i).getUniqueKeys().get(j) != copy.getEntities().get(i).getUniqueKeys().get(j)); + assertTrue(mb.getEntities().get(i).getUniqueKeys().get(j).getEntity() != copy.getEntities().get(i).getUniqueKeys().get(j).getEntity()); + + for (int k = 0; k < mb.getEntities().get(i).getUniqueKeys().get(j).getFields().size(); k++) { + assertTrue(mb.getEntities().get(i).getUniqueKeys().get(j).getFields().get(k) != copy.getEntities().get(i).getUniqueKeys().get(j).getFields().get(k)); + assertTrue(mb.getEntities().get(i).getUniqueKeys().get(j).getFields().get(k).getEntity() != copy.getEntities().get(i).getUniqueKeys().get(j).getFields().get(k).getEntity()); + } + } + + for (int j = 0; j < mb.getEntities().get(i).getKeys().size(); j++) { + assertTrue(mb.getEntities().get(i).getKeys().get(j) != copy.getEntities().get(i).getKeys().get(j)); + assertTrue(mb.getEntities().get(i).getKeys().get(j).getEntity() != copy.getEntities().get(i).getKeys().get(j).getEntity()); + + for (int k = 0; k < mb.getEntities().get(i).getKeys().get(j).getFields().size(); k++) { + assertTrue(mb.getEntities().get(i).getKeys().get(j).getFields().get(k) != copy.getEntities().get(i).getKeys().get(j).getFields().get(k)); + assertTrue(mb.getEntities().get(i).getKeys().get(j).getFields().get(k).getEntity() != copy.getEntities().get(i).getKeys().get(j).getFields().get(k).getEntity()); + } + } + + for (int j = 0; j < mb.getEntities().get(i).getIndexes().size(); j++) { + assertTrue(mb.getEntities().get(i).getIndexes().get(j) != copy.getEntities().get(i).getIndexes().get(j)); + assertTrue(mb.getEntities().get(i).getIndexes().get(j).getEntity() != copy.getEntities().get(i).getIndexes().get(j).getEntity()); + + for (int k = 0; k < mb.getEntities().get(i).getIndexes().get(j).getFields().size(); k++) { + assertTrue(mb.getEntities().get(i).getIndexes().get(j).getFields().get(k) != copy.getEntities().get(i).getIndexes().get(j).getFields().get(k)); + assertTrue(mb.getEntities().get(i).getIndexes().get(j).getFields().get(k).getEntity() != copy.getEntities().get(i).getIndexes().get(j).getFields().get(k).getEntity()); + } + } + } + } + + public static void debugHelper(ModelBuilder expected, ModelBuilder actual) { + //debug helpers + assertEquals(expected.getEntities().size(), actual.getEntities().size()); + for (int i = 0; i < expected.getEntities().size(); i++) { + var o = expected.getEntities().get(i); + var c = actual.getEntities().get(i); + if (!o.getTypeName().equals(c.getTypeName())) { + System.out.println("typenames differ: entity " + o.getName()); + System.out.println("ex: " + o.getTypeName()); + System.out.println("ac: " + c.getTypeName()); + } + if (!Objects.equals(o.getType(), c.getType())) { + System.out.println("types differ: entity " + o.getName()); + System.out.println("ex: " + o.getType()); + System.out.println("ac: " + c.getType()); + } + if (!o.getName().equals(c.getName())) { + System.out.println("names differ: entity " + o.getName()); + System.out.println("ex: " + o.getName()); + System.out.println("ac: " + c.getName()); + } + if (!Objects.equals(o.getPrimaryKey(), c.getPrimaryKey())) { + System.out.println("primary keys differ: entity " + o.getName()); + System.out.println("ex: " + o.getPrimaryKey()); + System.out.println("ac: " + c.getPrimaryKey()); + } + + assertEquals(o.getForeignKeys().size(), c.getForeignKeys().size()); + for (int j = 0; j < o.getForeignKeys().size(); j++) { + var ok = o.getForeignKeys().get(j); + var ck = c.getForeignKeys().get(j); + if (!Objects.equals(ok, ck)) { + System.out.println("foreign keys differ: entity " + o.getName()); + System.out.println("ex: " + ok); + System.out.println("ac: " + ck); + } + } + + assertEquals(o.getUniqueKeys().size(), c.getUniqueKeys().size()); + for (int j = 0; j < o.getUniqueKeys().size(); j++) { + var ok = o.getUniqueKeys().get(j); + var ck = c.getUniqueKeys().get(j); + if (!Objects.equals(ok, ck)) { + System.out.println("unique keys differ: entity " + o.getName()); + System.out.println("ex: " + ok); + System.out.println("ac: " + ck); + } + } + + assertEquals(o.getKeys().size(), c.getKeys().size()); + for (int j = 0; j < o.getKeys().size(); j++) { + var ok = o.getKeys().get(j); + var ck = c.getKeys().get(j); + if (!Objects.equals(ok, ck)) { + System.out.println("keys differ: entity " + o.getName()); + System.out.println("ex: " + ok); + System.out.println("ac: " + ck); + } + } + + assertEquals(o.getIndexes().size(), c.getIndexes().size()); + for (int j = 0; j < o.getIndexes().size(); j++) { + var ok = o.getIndexes().get(j); + var ck = c.getIndexes().get(j); + if (!Objects.equals(ok, ck)) { + System.out.println("indexes differ: entity " + o.getName()); + System.out.println("ex: " + ok); + System.out.println("ac: " + ck); + } + } + + assertEquals(o.getFields().size(), c.getFields().size()); + for (int j = 0; j < o.getIndexes().size(); j++) { + var of = o.getFields().get(j); + var cf = c.getFields().get(j); + + if (of.isModelField() != cf.isModelField()) { + System.out.println("is model fields differ: entity " + o.getName() + ", field " + of.getName()); + System.out.println("ex: " + of.isModelField()); + System.out.println("ac: " + cf.isModelField()); + } + if (of.isDatabaseField() != cf.isDatabaseField()) { + System.out.println("is database fields differ: entity " + o.getName() + ", field " + of.getName()); + System.out.println("ex: " + of.isDatabaseField()); + System.out.println("ac: " + cf.isDatabaseField()); + } + if (of.isNotNull() != cf.isNotNull()) { + System.out.println("is notnull differ: entity " + o.getName() + ", field " + of.getName()); + System.out.println("ex: " + of.isNotNull()); + System.out.println("ac: " + cf.isNotNull()); + } + if (!of.getEntity().getName().equals(cf.getEntity().getName())) { + System.out.println("entities differ: entity " + o.getName() + ", field " + of.getName()); + System.out.println("ex: " + of.getEntity().getName()); + System.out.println("ac: " + cf.getEntity().getName()); + } + if (!of.getTypeName().equals(cf.getTypeName())) { + System.out.println("typenames differ: entity " + o.getName() + ", field " + of.getName()); + System.out.println("ex: " + of.getTypeName()); + System.out.println("ac: " + cf.getTypeName()); + } + if (!Objects.equals(of.getType(), cf.getType())) { + System.out.println("types differ: entity " + o.getName() + ", field " + of.getName()); + System.out.println("ex: " + of.getType()); + System.out.println("ac: " + cf.getType()); + } + if (!Objects.equals(of.getField() == null ? null : of.getField().getName(), cf.getField() == null ? null : cf.getField().getName())) { + System.out.println("fields differ: entity " + o.getName() + ", field " + of.getName()); + System.out.println("ex: " + of.getField()); + System.out.println("ac: " + cf.getField()); + } + if (!Objects.equals(of.getForeignKeyModelLink() == null ? null : of.getForeignKeyModelLink().getName(), + cf.getForeignKeyModelLink() == null ? null : cf.getForeignKeyModelLink().getName())) { + System.out.println("fk model links differ: entity " + o.getName() + ", field " + of.getName()); + System.out.println("ex: " + (of.getForeignKeyModelLink() == null ? null : of.getForeignKeyModelLink().getName())); + System.out.println("ac: " + (cf.getForeignKeyModelLink() == null ? null : cf.getForeignKeyModelLink().getName())); + } + if (!of.getName().equals(cf.getName())) { + System.out.println("names differ: entity " + o.getName() + ", field " + of.getName()); + System.out.println("ex: " + of.getName()); + System.out.println("ac: " + cf.getName()); + } + +// if (!Objects.equals(of, cf)) { +// System.out.println("fields differ: entity " + o.getName() + ", field " + of.getName()); +// System.out.println("ex: " + of); +// System.out.println("ac: " + cf); +// } + } + } + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects2; + @Clazz(clazz = TestClass.class) + private DbSet objects1; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + @Key + public double d; + @Index + public float f; + @Unique + public long l; + private TestClass previous; + private TestClass next; + @Clazz(clazz = TestClass.class) + private List children; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public TestClass o; + public TestClass o2; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/ModelBuilderSimpleTest.java b/src/test/java/jef/model/ModelBuilderSimpleTest.java new file mode 100644 index 0000000..53e872f --- /dev/null +++ b/src/test/java/jef/model/ModelBuilderSimpleTest.java @@ -0,0 +1,43 @@ +package jef.model; + +import jef.DbSet; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.annotations.Index; +import jef.model.annotations.Key; +import jef.model.annotations.Unique; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ModelBuilderSimpleTest { + @Test + public void testClone() { + var mb = ModelBuilder.from(Ctx.class); + + assertEquals(1, mb.getEntities().size()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); + assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass.class) + private DbSet objects1; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } + +} \ No newline at end of file diff --git a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeTest.java b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeTest.java index c8664f2..1f88d74 100644 --- a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeTest.java +++ b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorExposeTest.java @@ -19,8 +19,8 @@ class ForeignKeyProcessorExposeTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(2, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); - assertEquals(TestClass2.class.getSimpleName(), mb.getEntity(TestClass2.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); + assertEquals("objects2", mb.getEntity(TestClass2.class).getName()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); diff --git a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorSelfReferenceTest.java b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorSelfReferenceTest.java index ff6e8cd..c830ee9 100644 --- a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorSelfReferenceTest.java +++ b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorSelfReferenceTest.java @@ -19,7 +19,7 @@ class ForeignKeyProcessorSelfReferenceTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); diff --git a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorSimpleTest.java b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorSimpleTest.java index 44ae7a2..6fa444f 100644 --- a/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorSimpleTest.java +++ b/src/test/java/jef/model/annotations/processors/ForeignKeyProcessorSimpleTest.java @@ -19,8 +19,8 @@ class ForeignKeyProcessorSimpleTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(2, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); - assertEquals(TestClass2.class.getSimpleName(), mb.getEntity(TestClass2.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); + assertEquals("objects2", mb.getEntity(TestClass2.class).getName()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); assertTrue(mb.getEntity(TestClass2.class).getFields().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass2.class))); assertTrue(mb.getEntity(TestClass.class).getForeignKeys().stream().allMatch(e -> e.getEntity() == mb.getEntity(TestClass.class))); diff --git a/src/test/java/jef/model/annotations/processors/IndexProcessorClassTest.java b/src/test/java/jef/model/annotations/processors/IndexProcessorClassTest.java index e8a30bf..5ddf888 100644 --- a/src/test/java/jef/model/annotations/processors/IndexProcessorClassTest.java +++ b/src/test/java/jef/model/annotations/processors/IndexProcessorClassTest.java @@ -19,7 +19,7 @@ class IndexProcessorClassTest { var mb = ModelBuilder.from(CtxIndexOnClass.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertFalse(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isIndex()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isIndex()); diff --git a/src/test/java/jef/model/annotations/processors/IndexProcessorFieldTest.java b/src/test/java/jef/model/annotations/processors/IndexProcessorFieldTest.java index a1ec81c..c299549 100644 --- a/src/test/java/jef/model/annotations/processors/IndexProcessorFieldTest.java +++ b/src/test/java/jef/model/annotations/processors/IndexProcessorFieldTest.java @@ -19,7 +19,7 @@ class IndexProcessorFieldTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertFalse(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isIndex()); assertFalse(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isIndex()); diff --git a/src/test/java/jef/model/annotations/processors/KeyProcessorClassTest.java b/src/test/java/jef/model/annotations/processors/KeyProcessorClassTest.java index e965604..141622a 100644 --- a/src/test/java/jef/model/annotations/processors/KeyProcessorClassTest.java +++ b/src/test/java/jef/model/annotations/processors/KeyProcessorClassTest.java @@ -19,7 +19,7 @@ class KeyProcessorClassTest { var mb = ModelBuilder.from(CtxKeyOnClass.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertFalse(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isKey()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isKey()); diff --git a/src/test/java/jef/model/annotations/processors/KeyProcessorFieldTest.java b/src/test/java/jef/model/annotations/processors/KeyProcessorFieldTest.java index 287433b..54e17be 100644 --- a/src/test/java/jef/model/annotations/processors/KeyProcessorFieldTest.java +++ b/src/test/java/jef/model/annotations/processors/KeyProcessorFieldTest.java @@ -19,7 +19,7 @@ class KeyProcessorFieldTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertFalse(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isKey()); assertFalse(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isKey()); diff --git a/src/test/java/jef/model/annotations/processors/NotNullProcessorTest.java b/src/test/java/jef/model/annotations/processors/NotNullProcessorTest.java index d1cf32f..e50eb78 100644 --- a/src/test/java/jef/model/annotations/processors/NotNullProcessorTest.java +++ b/src/test/java/jef/model/annotations/processors/NotNullProcessorTest.java @@ -19,7 +19,7 @@ class NotNullProcessorTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isNotNull()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isNotNull()); diff --git a/src/test/java/jef/model/annotations/processors/UniqueProcessorClassTest.java b/src/test/java/jef/model/annotations/processors/UniqueProcessorClassTest.java index 9df0dde..3651a8e 100644 --- a/src/test/java/jef/model/annotations/processors/UniqueProcessorClassTest.java +++ b/src/test/java/jef/model/annotations/processors/UniqueProcessorClassTest.java @@ -19,7 +19,7 @@ class UniqueProcessorClassTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertFalse(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isUnique()); assertTrue(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isUnique()); diff --git a/src/test/java/jef/model/annotations/processors/UniqueProcessorFieldTest.java b/src/test/java/jef/model/annotations/processors/UniqueProcessorFieldTest.java index ee663ce..023cae2 100644 --- a/src/test/java/jef/model/annotations/processors/UniqueProcessorFieldTest.java +++ b/src/test/java/jef/model/annotations/processors/UniqueProcessorFieldTest.java @@ -19,7 +19,7 @@ class UniqueProcessorFieldTest { var mb = ModelBuilder.from(Ctx.class); assertEquals(1, mb.getEntities().size()); - assertEquals(TestClass.class.getSimpleName(), mb.getEntity(TestClass.class).getName()); + assertEquals("objects1", mb.getEntity(TestClass.class).getName()); assertFalse(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("i")).findFirst().get().isUnique()); assertFalse(mb.getEntity(TestClass.class).getFields().stream().filter(e -> e.getName().equals("d")).findFirst().get().isUnique()); diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddEntityTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddEntityTest.java new file mode 100644 index 0000000..293a57b --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddEntityTest.java @@ -0,0 +1,111 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.constraints.ForeignKeyConstraint; +import jef.model.constraints.PrimaryKeyConstraint; +import jef.model.migration.operation.AddForeignKeyOperation; +import jef.model.migration.operation.AddPrimaryKeyOperation; +import jef.model.migration.operation.AddTableOperation; +import jef.model.migration.operation.DropConstraintOperation; +import jef.model.migration.operation.DropTableOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorAddEntityTest extends MigrationCreatorTestBase { + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + var ent = to.getOrCreateEntity("AddedEntity"); + ent.getOrCreateField("id", int.class.getName()); + ent.getOrCreateField("addedField", int.class.getName()); + ent.setPrimaryKey(new PrimaryKeyConstraint(ent, List.of(ent.getField("id")))); + ent.addForeignKey(new ForeignKeyConstraint(ent, List.of(ent.getField("addedField")), ent, List.of(ent.getField("id")), ForeignKeyConstraint.Action.CASCADE, ForeignKeyConstraint.Action.CASCADE)); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(3, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddTableOperation o + && o.getTable().equals("AddedEntity") + && o.getFields().size() == 2 + && o.getFields().stream().filter(f -> f.build().getField().equals("id")).count() == 1 + && o.getFields().stream().filter(f -> f.build().getField().equals("addedField")).count() == 1) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddPrimaryKeyOperation o + && o.getTable().equals("AddedEntity") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("id")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddForeignKeyOperation o + && o.getTable().equals("AddedEntity") + && o.getReferencedTable().equals("AddedEntity") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("addedField") + && o.getReferencedFields().size() == 1 + && o.getReferencedFields().get(0).equals("id")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(3, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("AddedEntity") + && o.getName().equals("FK_AddedEntity_AddedEntity_addedField")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("AddedEntity") + && o.getName().equals("PRIMARY")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropTableOperation o + && o.getTable().equals("AddedEntity")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddFieldTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddFieldTest.java new file mode 100644 index 0000000..9ccb8a0 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddFieldTest.java @@ -0,0 +1,95 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.constraints.ForeignKeyConstraint; +import jef.model.migration.operation.AddFieldOperation; +import jef.model.migration.operation.AddForeignKeyOperation; +import jef.model.migration.operation.DropConstraintOperation; +import jef.model.migration.operation.DropFieldOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorAddFieldTest extends MigrationCreatorTestBase{ + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + var ent = to.getEntity(TestClass2.class); + ent.getOrCreateField("addedField", int.class.getName()); + ent.addForeignKey(new ForeignKeyConstraint(ent, List.of(ent.getField("addedField")), ent, List.of(ent.getField("i")), ForeignKeyConstraint.Action.CASCADE, ForeignKeyConstraint.Action.CASCADE)); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(2, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddFieldOperation o + && o.getTable().equals("objects1") + && o.getField().equals("addedField")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddForeignKeyOperation o + && o.getTable().equals("objects1") + && o.getReferencedTable().equals("objects1") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("addedField") + && o.getReferencedFields().size() == 1 + && o.getReferencedFields().get(0).equals("i")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(2, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("objects1") + && o.getName().equals("FK_objects1_objects1_addedField")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropFieldOperation o + && o.getTable().equals("objects1") + && o.getField().equals("addedField")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddForeignKeyTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddForeignKeyTest.java new file mode 100644 index 0000000..b178230 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddForeignKeyTest.java @@ -0,0 +1,83 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.constraints.ForeignKeyConstraint; +import jef.model.migration.operation.AddForeignKeyOperation; +import jef.model.migration.operation.DropConstraintOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorAddForeignKeyTest extends MigrationCreatorTestBase{ + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + var ent = to.getEntity(TestClass.class); + ent.addForeignKey(new ForeignKeyConstraint(ent, List.of(ent.getField("i2")), ent, List.of(ent.getField("i")), ForeignKeyConstraint.Action.CASCADE, ForeignKeyConstraint.Action.CASCADE)); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(1, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddForeignKeyOperation o + && o.getTable().equals("nested") + && o.getReferencedTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i2") + && o.getReferencedFields().size() == 1 + && o.getReferencedFields().get(0).equals("i")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(1, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("FK_nested_nested_i2")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public int i2 = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddIndexTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddIndexTest.java new file mode 100644 index 0000000..f4d208e --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddIndexTest.java @@ -0,0 +1,78 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.migration.operation.AddIndexOperation; +import jef.model.migration.operation.DropConstraintOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorAddIndexTest extends MigrationCreatorTestBase{ + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + var ent = to.getEntity(TestClass.class); + ent.getField("d").setIndex(true); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(1, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddIndexOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("d")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(1, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("I_nested_d")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddKeyTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddKeyTest.java new file mode 100644 index 0000000..7ec57a9 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddKeyTest.java @@ -0,0 +1,78 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.migration.operation.AddKeyOperation; +import jef.model.migration.operation.DropConstraintOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorAddKeyTest extends MigrationCreatorTestBase{ + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + var ent = to.getEntity(TestClass.class); + ent.getField("d").setKey(true); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(1, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("d")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(1, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("K_nested_d")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorAddUniqueTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorAddUniqueTest.java new file mode 100644 index 0000000..c8a8541 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorAddUniqueTest.java @@ -0,0 +1,78 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.migration.operation.AddUniqueKeyOperation; +import jef.model.migration.operation.DropConstraintOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorAddUniqueTest extends MigrationCreatorTestBase{ + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + var ent = to.getEntity(TestClass.class); + ent.getField("d").setUnique(true); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(1, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddUniqueKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("d")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(1, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("U_nested_d")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorEmptyTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorEmptyTest.java new file mode 100644 index 0000000..a8e401b --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorEmptyTest.java @@ -0,0 +1,61 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.annotations.Index; +import jef.model.annotations.Key; +import jef.model.annotations.Unique; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorEmptyTest extends MigrationCreatorTestBase{ + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "EmptyMigration", "test",new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + assertEquals(0, res.getStepsUp().size()); + assertEquals(0, res.getStepsDown().size()); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + @Index + public double d; + @Unique + public float f; + @Key + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorInitialMigrationTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorInitialMigrationTest.java new file mode 100644 index 0000000..3155f37 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorInitialMigrationTest.java @@ -0,0 +1,130 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.migration.operation.AddForeignKeyOperation; +import jef.model.migration.operation.AddPrimaryKeyOperation; +import jef.model.migration.operation.AddTableOperation; +import jef.model.migration.operation.DropConstraintOperation; +import jef.model.migration.operation.DropTableOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorInitialMigrationTest extends MigrationCreatorTestBase { + @Test + public void test() { + var from = new ModelBuilder(List.of()); + var to = ModelBuilder.from(Ctx.class); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "InitialMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(5, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddTableOperation o + && o.getTable().equals("objects1") + && o.getFields().size() == 1 + && o.getFields().get(0).build().getField().equals("i")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddTableOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 6 + && o.getFields().stream().filter(f -> f.build().getField().equals("d")).count() == 1 + && o.getFields().stream().filter(f -> f.build().getField().equals("f")).count() == 1 + && o.getFields().stream().filter(f -> f.build().getField().equals("i")).count() == 1 + && o.getFields().stream().filter(f -> f.build().getField().equals("l")).count() == 1 + && o.getFields().stream().filter(f -> f.build().getField().equals("o")).count() == 1 + && o.getFields().stream().filter(f -> f.build().getField().equals("nestedI")).count() == 1) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddPrimaryKeyOperation o + && o.getTable().equals("objects1") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddPrimaryKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddForeignKeyOperation o + && o.getTable().equals("nested") + && o.getReferencedTable().equals("objects1") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("nestedI") + && o.getReferencedFields().size() == 1 + && o.getReferencedFields().get(0).equals("i")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(5, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("FK_nested_objects1_nestedI")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("PRIMARY")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("objects1") + && o.getName().equals("PRIMARY")) + .count()); + + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropTableOperation o + && o.getTable().equals("nested")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropTableOperation o + && o.getTable().equals("objects1")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorRenameEntityTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameEntityTest.java new file mode 100644 index 0000000..74f23a5 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameEntityTest.java @@ -0,0 +1,129 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.migration.operation.AddForeignKeyOperation; +import jef.model.migration.operation.AddPrimaryKeyOperation; +import jef.model.migration.operation.DropConstraintOperation; +import jef.model.migration.operation.RenameTableOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorRenameEntityTest extends MigrationCreatorTestBase { + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + var ent = to.getEntity(TestClass.class); + ent.setName("d2"); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(5, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("FK_nested_objects1_nestedI")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("PRIMARY")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof RenameTableOperation o + && o.getOldName().equals("nested") + && o.getNewName().equals("d2")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddPrimaryKeyOperation o + && o.getTable().equals("d2") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddForeignKeyOperation o + && o.getTable().equals("d2") + && o.getReferencedTable().equals("objects1") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("nestedI") + && o.getReferencedFields().size() == 1 + && o.getReferencedFields().get(0).equals("i")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(5, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("d2") + && o.getName().equals("FK_d2_objects1_nestedI")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("d2") + && o.getName().equals("PRIMARY")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof RenameTableOperation o + && o.getOldName().equals("d2") + && o.getNewName().equals("nested")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof AddPrimaryKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof AddForeignKeyOperation o + && o.getTable().equals("nested") + && o.getReferencedTable().equals("objects1") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("nestedI") + && o.getReferencedFields().size() == 1 + && o.getReferencedFields().get(0).equals("i")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldConstraintsTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldConstraintsTest.java new file mode 100644 index 0000000..dcc7347 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldConstraintsTest.java @@ -0,0 +1,210 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.ForeignKey; +import jef.model.annotations.Id; +import jef.model.annotations.Index; +import jef.model.annotations.Key; +import jef.model.annotations.Unique; +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.AddUniqueKeyOperation; +import jef.model.migration.operation.DropConstraintOperation; +import jef.model.migration.operation.RenameFieldOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorRenameFieldConstraintsTest extends MigrationCreatorTestBase { + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + to.getEntity(TestClass.class).getField("i").setName("i2"); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(11, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("FK_nested_nested_iFk")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("PRIMARY")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("U_nested_i")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("K_nested_i")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("I_nested_i")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof RenameFieldOperation o + && o.getTable().equals("nested") + && o.getOldName().equals("i") + && o.getNewName().equals("i2")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddPrimaryKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i2")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddForeignKeyOperation o + && o.getTable().equals("nested") + && o.getReferencedTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("iFk") + && o.getReferencedFields().size() == 1 + && o.getReferencedFields().get(0).equals("i2")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddUniqueKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i2")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i2")) + .count()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof AddIndexOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i2")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(11, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("FK_nested_nested_iFk")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("PRIMARY")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("U_nested_i2")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("K_nested_i2")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof DropConstraintOperation o + && o.getTable().equals("nested") + && o.getName().equals("I_nested_i2")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof RenameFieldOperation o + && o.getTable().equals("nested") + && o.getOldName().equals("i2") + && o.getNewName().equals("i")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof AddPrimaryKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof AddForeignKeyOperation o + && o.getTable().equals("nested") + && o.getReferencedTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("iFk") + && o.getReferencedFields().size() == 1 + && o.getReferencedFields().get(0).equals("i")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof AddUniqueKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof AddKeyOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i")) + .count()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof AddIndexOperation o + && o.getTable().equals("nested") + && o.getFields().size() == 1 + && o.getFields().get(0).equals("i")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + + + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + @Index + @Unique + @Key + public int i = 1; + @ForeignKey(entity = TestClass.class, getterOrField = "i") + public int iFk = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldTest.java b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldTest.java new file mode 100644 index 0000000..9aa1843 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorRenameFieldTest.java @@ -0,0 +1,78 @@ +package jef.model.migration.creator; + +import jef.DbSet; +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.annotations.Clazz; +import jef.model.annotations.Id; +import jef.model.migration.operation.RenameFieldOperation; +import jef.serializable.SerializableObject; +import lombok.Getter; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorRenameFieldTest extends MigrationCreatorTestBase { + @Test + public void test() { + var from = ModelBuilder.from(Ctx.class); + var to = ModelBuilder.from(Ctx.class); + var ent = to.getEntity(TestClass.class); + ent.getField("d").setName("d2"); + var mc = new MigrationCreator(); + var res = mc.createMigration(from, to, "SomeMigration", "test", new ModelBuilderGenerator(from, "Current", "test").generate().getJava()); + try { + validateUp(res); + validateDown(res); + } catch (Throwable t) { + System.out.println(res.getMigration()); + throw t; + } + validateMigration(res, from, to); + } + + private void validateUp(MigrationCreator.Result res) { + assertEquals(1, res.getStepsUp().size()); + assertEquals(1, res.getStepsUp().stream() + .filter(e -> e instanceof RenameFieldOperation o + && o.getTable().equals("nested") + && o.getOldName().equals("d") + && o.getNewName().equals("d2")) + .count()); + } + + private void validateDown(MigrationCreator.Result res) { + assertEquals(1, res.getStepsDown().size()); + assertEquals(1, res.getStepsDown().stream() + .filter(e -> e instanceof RenameFieldOperation o + && o.getTable().equals("nested") + && o.getOldName().equals("d2") + && o.getNewName().equals("d")) + .count()); + } + + public static class Ctx extends DbContext { + @Clazz(clazz = TestClass2.class) + private DbSet objects1; + } + + @Getter + public static class TestClass2 extends SerializableObject { + @Id + public int i = 1; + @Clazz(clazz = TestClass.class) + public List nested; + } + + @Getter + public static class TestClass extends SerializableObject { + @Id + public int i = 1; + public Object o = new Object(); + public double d; + public float f; + public long l; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/MigrationCreatorTestBase.java b/src/test/java/jef/model/migration/creator/MigrationCreatorTestBase.java new file mode 100644 index 0000000..b7cfdb1 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/MigrationCreatorTestBase.java @@ -0,0 +1,142 @@ +package jef.model.migration.creator; + +import jef.model.DbContext; +import jef.model.ModelBuilder; +import jef.model.ModelBuilderCloneTest; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MigrationCreatorTestBase { + private static final File JAVA_FILES_DIR = new File("target/jef-tests/sources"); + private static final File CLASS_FILES_DIR = new File("target/jef-tests/classes"); + + public void validateMigration(MigrationCreator.Result result, ModelBuilder from, ModelBuilder to) { + validateMigrationJava(result.getMigration()); + validatePreMigrationSnapshotJava(result.getMigrationSnapshot(), from); + validatePostMigrationSnapshotJava(result.getCurrentSnapshot(), to); + } + + public void validateMigrationJava(String migrationJava) { + try { + var packageName = findPackageName(migrationJava); + var className = findClassName(migrationJava); + compile(packageName, className, migrationJava); + var clazz = loadClass(packageName, className); + //for migration just test if it's compiling + } catch (AssertionError | RuntimeException e) { + throw e; + } catch (Throwable t) { + throw new RuntimeException("Failed to load generated class", t); + } + } + + public void validatePreMigrationSnapshotJava(String snapshotJava, ModelBuilder from) { + try { + var packageName = findPackageName(snapshotJava); + var className = findClassName(snapshotJava); + compile(packageName, className, snapshotJava); + var clazz = (Class) loadClass(packageName, className); + var mb = ModelBuilder.from(clazz); + ModelBuilderCloneTest.debugHelper(from, mb); + assertEquals(from, mb); + } catch (AssertionError | RuntimeException e) { + throw e; + } catch (Throwable t) { + throw new RuntimeException("Failed to load generated class", t); + } + } + + public void validatePostMigrationSnapshotJava(String snapshotJava, ModelBuilder to) { + try { + var packageName = findPackageName(snapshotJava); + var className = findClassName(snapshotJava); + compile(packageName, className, snapshotJava); + var clazz = (Class) loadClass(packageName, className); + var mb = ModelBuilder.from(clazz); + assertEquals(to, mb); + } catch (AssertionError | RuntimeException e) { + throw e; + } catch (Throwable t) { + throw new RuntimeException("Failed to load generated class", t); + } + } + + private File compile(String packageName, String className, String java) { + try {//src + File srcdir = new File(getSourcesFilesDir(), packageName.replace(".", File.separator)); + srcdir.mkdirs(); + File srcf = new File(srcdir, className + ".java"); + Files.writeString(srcf.toPath(), java, StandardCharsets.UTF_8, + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); + + //dest + File destdir = getClassFilesDir(); + var process = new ProcessBuilder() + .command("javac", //TODO use javac for current jdk + "-cp", "target/classes" + File.pathSeparator + "target/test-classes", + "-encoding", "UTF8", + "-g", //debug symbols + "-d", destdir.getPath(), //target + srcf.getPath() + ) + .inheritIO() + .start(); + process.waitFor(10, TimeUnit.SECONDS); + var exitCode = process.exitValue(); + assertEquals(0, exitCode, "Compilation failed"); + + return new File(new File(destdir, packageName.replace(".", File.separator)), srcf.getName().replace(".java", ".class")); + } catch (AssertionError | RuntimeException e) { + throw e; + } catch (Throwable t) { + throw new RuntimeException("Error while compiling generated class", t); + } + } + + private Class loadClass(String packageName, String className) { + try { + var cl = new URLClassLoader(new URL[]{getClassFilesDir().toURL()}); + return cl.loadClass(packageName + "." + className); + } catch (AssertionError | RuntimeException e) { + throw e; + } catch (Throwable t) { + throw new RuntimeException("Failed to load generated class", t); + } + } + + private String findPackageName(String java) { + Pattern p = Pattern.compile("package ([^;]+);"); + Matcher m = p.matcher(java); + m.find(); + return m.group(1); + } + + private String findClassName(String java) { + Pattern p = Pattern.compile("public class ([^ ]+)"); + Matcher m = p.matcher(java); + m.find(); + return m.group(1); + } + + private File getSourcesFilesDir() { + File srcdir = new File(JAVA_FILES_DIR, getClass().getSimpleName()); + srcdir.mkdirs(); + return srcdir; + } + + private File getClassFilesDir() { + File destdir = new File(CLASS_FILES_DIR, getClass().getSimpleName()); + destdir.mkdirs(); + return destdir; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddEntityTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddEntityTest.java new file mode 100644 index 0000000..560a51a --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddEntityTest.java @@ -0,0 +1,45 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorAddEntityTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(0, cd.getFrom().getEntities().size()); + + assertEquals(2, cd.getTo().getEntities().size()); + assertEquals(1, cd.getTo().getEntities().get(0).getFields().size()); + assertEquals(1, cd.getTo().getEntities().get(1).getFields().size()); + assertEquals("i2", cd.getTo().getEntities().get(0).getFields().get(0).getName()); + assertEquals("i3", cd.getTo().getEntities().get(1).getFields().get(0).getName()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass2"); + mb.getOrCreateEntity("TestClass2").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass3"); + mb.getOrCreateEntity("TestClass3").getOrCreateField("i3", int.class.getName()) + .setNotNull(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddFieldTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddFieldTest.java new file mode 100644 index 0000000..2e9b61c --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddFieldTest.java @@ -0,0 +1,43 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorAddFieldTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(0, cd.getFrom().getEntities().get(0).getFields().size()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(2, cd.getTo().getEntities().get(0).getFields().size()); + assertEquals("i2", cd.getTo().getEntities().get(0).getFields().get(0).getName()); + assertEquals("i3", cd.getTo().getEntities().get(0).getFields().get(1).getName()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i3", int.class.getName()) + .setNotNull(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddForeignKeyTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddForeignKeyTest.java new file mode 100644 index 0000000..f4e0709 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddForeignKeyTest.java @@ -0,0 +1,48 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import jef.model.constraints.ForeignKeyConstraint; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorAddForeignKeyTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(0, cd.getFrom().getEntities().get(0).getForeignKeys().size()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(1, cd.getTo().getEntities().get(0).getForeignKeys().size()); + assertEquals("FK_TestClass_TestClass_i2", cd.getTo().getEntities().get(0).getForeignKeys().get(0).getName()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + var ent = mb.getOrCreateEntity("TestClass"); + ent.getOrCreateField("i", int.class.getName()) + .setNotNull(true); + ent.getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + ent.addForeignKey(new ForeignKeyConstraint(ent, List.of(ent.getField("i2")), + ent, List.of(ent.getField("i")), + ForeignKeyConstraint.Action.CASCADE, ForeignKeyConstraint.Action.CASCADE)); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddIndexTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddIndexTest.java new file mode 100644 index 0000000..02ce9ba --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddIndexTest.java @@ -0,0 +1,43 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorAddIndexTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(0, cd.getFrom().getEntities().get(0).getIndexes().size()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(1, cd.getTo().getEntities().get(0).getIndexes().size()); + assertEquals("I_TestClass_i", cd.getTo().getEntities().get(0).getIndexes().get(0).getName()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()).setIndex(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddKeyTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddKeyTest.java new file mode 100644 index 0000000..4f58548 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddKeyTest.java @@ -0,0 +1,43 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorAddKeyTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(0, cd.getFrom().getEntities().get(0).getKeys().size()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(1, cd.getTo().getEntities().get(0).getKeys().size()); + assertEquals("K_TestClass_i", cd.getTo().getEntities().get(0).getKeys().get(0).getName()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()).setKey(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddPrimaryKeyTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddPrimaryKeyTest.java new file mode 100644 index 0000000..f882a50 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddPrimaryKeyTest.java @@ -0,0 +1,48 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import jef.model.constraints.PrimaryKeyConstraint; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +class ModelChangeDetectorAddPrimaryKeyTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertNull(cd.getFrom().getEntities().get(0).getPrimaryKey()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertNotNull(cd.getTo().getEntities().get(0).getPrimaryKey()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").setPrimaryKey(new PrimaryKeyConstraint(mb.getOrCreateEntity("TestClass"), + List.of(mb.getOrCreateEntity("TestClass").getField("i")))); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddUniqueTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddUniqueTest.java new file mode 100644 index 0000000..ceb5983 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorAddUniqueTest.java @@ -0,0 +1,43 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorAddUniqueTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(0, cd.getFrom().getEntities().get(0).getUniqueKeys().size()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(1, cd.getTo().getEntities().get(0).getUniqueKeys().size()); + assertEquals("U_TestClass_i", cd.getTo().getEntities().get(0).getUniqueKeys().get(0).getName()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()).setUnique(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropEntityTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropEntityTest.java new file mode 100644 index 0000000..c740b3b --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropEntityTest.java @@ -0,0 +1,45 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorDropEntityTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(2, cd.getFrom().getEntities().size()); + assertEquals(1, cd.getFrom().getEntities().get(0).getFields().size()); + assertEquals(1, cd.getFrom().getEntities().get(1).getFields().size()); + assertEquals("i2", cd.getFrom().getEntities().get(0).getFields().get(0).getName()); + assertEquals("i3", cd.getFrom().getEntities().get(1).getFields().get(0).getName()); + + assertEquals(0, cd.getTo().getEntities().size()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass2"); + mb.getOrCreateEntity("TestClass2").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass3"); + mb.getOrCreateEntity("TestClass3").getOrCreateField("i3", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropFieldTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropFieldTest.java new file mode 100644 index 0000000..15814f4 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropFieldTest.java @@ -0,0 +1,43 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorDropFieldTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(2, cd.getFrom().getEntities().get(0).getFields().size()); + assertEquals("i2", cd.getFrom().getEntities().get(0).getFields().get(0).getName()); + assertEquals("i3", cd.getFrom().getEntities().get(0).getFields().get(1).getName()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(0, cd.getTo().getEntities().get(0).getFields().size()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i3", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropForeignKeyTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropForeignKeyTest.java new file mode 100644 index 0000000..b5a6530 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropForeignKeyTest.java @@ -0,0 +1,48 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import jef.model.constraints.ForeignKeyConstraint; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorDropForeignKeyTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(1, cd.getFrom().getEntities().get(0).getForeignKeys().size()); + assertEquals("FK_TestClass_TestClass_i2", cd.getFrom().getEntities().get(0).getForeignKeys().get(0).getName()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(0, cd.getTo().getEntities().get(0).getForeignKeys().size()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + var ent = mb.getOrCreateEntity("TestClass"); + ent.getOrCreateField("i", int.class.getName()) + .setNotNull(true); + ent.getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + ent.addForeignKey(new ForeignKeyConstraint(ent, List.of(ent.getField("i2")), + ent, List.of(ent.getField("i")), + ForeignKeyConstraint.Action.CASCADE, ForeignKeyConstraint.Action.CASCADE)); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropIndexTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropIndexTest.java new file mode 100644 index 0000000..b8a8d94 --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropIndexTest.java @@ -0,0 +1,43 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorDropIndexTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(1, cd.getFrom().getEntities().get(0).getIndexes().size()); + assertEquals("I_TestClass_i", cd.getFrom().getEntities().get(0).getIndexes().get(0).getName()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(0, cd.getTo().getEntities().get(0).getIndexes().size()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()).setIndex(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropKeyTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropKeyTest.java new file mode 100644 index 0000000..95fa24f --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropKeyTest.java @@ -0,0 +1,43 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorDropKeyTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(1, cd.getFrom().getEntities().get(0).getKeys().size()); + assertEquals("K_TestClass_i", cd.getFrom().getEntities().get(0).getKeys().get(0).getName()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(0, cd.getTo().getEntities().get(0).getKeys().size()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()).setKey(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropPrimaryKeyTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropPrimaryKeyTest.java new file mode 100644 index 0000000..a1fffaa --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropPrimaryKeyTest.java @@ -0,0 +1,48 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import jef.model.constraints.PrimaryKeyConstraint; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +class ModelChangeDetectorDropPrimaryKeyTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertNotNull(cd.getFrom().getEntities().get(0).getPrimaryKey()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertNull(cd.getTo().getEntities().get(0).getPrimaryKey()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").setPrimaryKey(new PrimaryKeyConstraint(mb.getOrCreateEntity("TestClass"), + List.of(mb.getOrCreateEntity("TestClass").getField("i")))); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropUniqueTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropUniqueTest.java new file mode 100644 index 0000000..2ab489c --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorDropUniqueTest.java @@ -0,0 +1,43 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelChangeDetectorDropUniqueTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + + assertEquals(1, cd.getFrom().getEntities().size()); + assertEquals(1, cd.getFrom().getEntities().get(0).getUniqueKeys().size()); + assertEquals("U_TestClass_i", cd.getFrom().getEntities().get(0).getUniqueKeys().get(0).getName()); + + assertEquals(1, cd.getTo().getEntities().size()); + assertEquals(0, cd.getTo().getEntities().get(0).getUniqueKeys().size()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()).setUnique(true); + return mb; + } + + private ModelBuilder createTo() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } +} \ No newline at end of file diff --git a/src/test/java/jef/model/migration/creator/ModelChangeDetectorNoChangeTest.java b/src/test/java/jef/model/migration/creator/ModelChangeDetectorNoChangeTest.java new file mode 100644 index 0000000..c6738aa --- /dev/null +++ b/src/test/java/jef/model/migration/creator/ModelChangeDetectorNoChangeTest.java @@ -0,0 +1,34 @@ +package jef.model.migration.creator; + +import jef.model.ModelBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ModelChangeDetectorNoChangeTest { + @Test + public void test() { + var from = createFrom(); + var to = createTo(); + var cd = new ModelChangeDetector(from, to).detect(); + assertTrue(cd.getFrom().getEntities().isEmpty()); + assertTrue(cd.getTo().getEntities().isEmpty()); + } + + private ModelBuilder createFrom() { + var mb = new ModelBuilder(); + mb.getOrCreateEntity("TestClass"); + mb.getOrCreateEntity("TestClass").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass2"); + mb.getOrCreateEntity("TestClass2").getOrCreateField("i", int.class.getName()) + .setNotNull(true); + mb.getOrCreateEntity("TestClass2").getOrCreateField("i2", int.class.getName()) + .setNotNull(true); + return mb; + } + + private ModelBuilder createTo() { + return createFrom(); + } +} \ No newline at end of file