added basic model validation
This commit is contained in:
@@ -1,6 +1,82 @@
|
||||
package jef.model;
|
||||
|
||||
import jef.model.constraints.ForeignKeyConstraint;
|
||||
import jef.serializable.SerializableObject;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class DbContext {
|
||||
private static final String ILLEGAL_CHARACTERS = "\"'`,.";//TODO also validate this in modelbuilder/dbentity/dbfield
|
||||
|
||||
public void onModelCreate(ModelBuilder mb) {
|
||||
}
|
||||
|
||||
public void onModelValidate(ModelBuilder mb) {
|
||||
var errors = new ArrayList<String>();
|
||||
|
||||
//check for illegal characters
|
||||
for (var entity : mb.getEntities()) {
|
||||
if (entity.getName().matches("[" + ILLEGAL_CHARACTERS + "]")) {
|
||||
errors.add("DbEntity " + entity.getTypeName() + " has illegal characters in its name: " + entity.getName());
|
||||
}
|
||||
for (var field : entity.getFields()) {
|
||||
if (field.getName().matches("[" + ILLEGAL_CHARACTERS + "]")) {
|
||||
var fieldname = Optional.ofNullable(field.getField()).map(Field::getName).orElse(field.getName());
|
||||
errors.add("DbField " + entity.getTypeName() + "::" + fieldname + " has illegal characters in its name: " + entity.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check for duplicate entity names
|
||||
var entityNameMap = new HashMap<String, DbEntity<?>>();
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
var name = entity.getName().toLowerCase();
|
||||
if (entityNameMap.containsKey(name)) {
|
||||
errors.add("Duplicate DbEntity with name " + entity.getName() + ": " + entity.getTypeName() + " and " + entityNameMap.get(name).getTypeName());
|
||||
} else {
|
||||
entityNameMap.put(name, entity);
|
||||
}
|
||||
}
|
||||
|
||||
//check for duplicate field names
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
var fieldNameMap = new HashMap<String, DbEntity<?>>();
|
||||
for (DbField<?> field : entity.getFields()) {
|
||||
var name = field.getName().toLowerCase();
|
||||
if (fieldNameMap.containsKey(name)) {
|
||||
errors.add("Duplicate DbField in entity " + entity.getTypeName() + " with name " + field.getName() + ": " + field.getName() + " and " + fieldNameMap.get(name).getName());
|
||||
} else {
|
||||
fieldNameMap.put(name, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check foreign keys for existing primary keys
|
||||
for (DbEntity<? extends SerializableObject> entity : mb.getEntities()) {
|
||||
for (ForeignKeyConstraint foreignKey : entity.getForeignKeys()) {
|
||||
//verify primary key exists
|
||||
if (foreignKey.getReferencedEntity().getPrimaryKey() == null) {
|
||||
errors.add("DbEntity " + entity.getTypeName() + " has a foreign key constraint to " + foreignKey.getReferencedEntity().getTypeName()
|
||||
+ " which does not have primary key");
|
||||
}
|
||||
//verify field count, names, types, and order
|
||||
else if (!foreignKey.getReferencedEntity().getPrimaryKey().getFields().equals(foreignKey.getReferencedFields())) {
|
||||
errors.add("DbEntity " + entity.getTypeName() + " has a foreign key constraint to " + foreignKey.getReferencedEntity().getTypeName()
|
||||
+ " where the specified fields do not match: referring fields ["
|
||||
+ foreignKey.getReferencedFields().stream().map(DbField::getName).collect(Collectors.joining(", "))
|
||||
+ "] but found referenced fields ["
|
||||
+ foreignKey.getReferencedEntity().getPrimaryKey().getFields().stream().map(DbField::getName).collect(Collectors.joining(", "))
|
||||
+ "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
throw new ModelException(String.join("\n", errors));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,17 +179,12 @@ public class DbEntity<T extends SerializableObject> {
|
||||
return this.indexes.remove(index);
|
||||
}
|
||||
|
||||
private <R> String extractFieldName(SerializableFunction<T, R> getter) {
|
||||
try {
|
||||
var expr = new AsmParser(getter).parse();
|
||||
if (expr.getType() != Expression.Type.INTERMEDIATE_FIELD) {
|
||||
throw new RuntimeException(expr.getClass().getSimpleName() + " is not a field expression");
|
||||
}
|
||||
var name = ((IntermediateFieldExpression) expr).getName();
|
||||
return name;
|
||||
} catch (AsmParseException e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
/**
|
||||
* Does not validate foreign key integrity
|
||||
* @param primaryKey
|
||||
*/
|
||||
public void setPrimaryKey(PrimaryKeyConstraint primaryKey) {
|
||||
this.primaryKey = primaryKey;
|
||||
}
|
||||
|
||||
public void addForeignKey(ForeignKeyConstraint foreignKey) {
|
||||
@@ -216,6 +211,19 @@ public class DbEntity<T extends SerializableObject> {
|
||||
}
|
||||
}
|
||||
|
||||
private <R> String extractFieldName(SerializableFunction<T, R> getter) {
|
||||
try {
|
||||
var expr = new AsmParser(getter).parse();
|
||||
if (expr.getType() != Expression.Type.INTERMEDIATE_FIELD) {
|
||||
throw new RuntimeException(expr.getClass().getSimpleName() + " is not a field expression");
|
||||
}
|
||||
var name = ((IntermediateFieldExpression) expr).getName();
|
||||
return name;
|
||||
} catch (AsmParseException e) {
|
||||
throw new RuntimeException("Invalid expression", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
@@ -56,6 +56,7 @@ public class ModelBuilder {
|
||||
|
||||
var instance = context.getDeclaredConstructor().newInstance();//TODO find constructor, //TODO optionally with config
|
||||
instance.onModelCreate(mb);
|
||||
instance.onModelValidate(mb);
|
||||
|
||||
return mb;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ 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();
|
||||
@@ -70,18 +70,6 @@ public class MigrationCreator {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user