added basic model validation

This commit is contained in:
wea_ondara
2022-08-14 16:17:53 +02:00
parent dc01753fec
commit c6264b8e3e
5 changed files with 97 additions and 25 deletions

View File

@@ -1,6 +1,5 @@
# Todo # Todo
## Next steps ## Next steps
- onvalidate function, check illegal chars in names, name collitions
- use builders for entities/fields/keys - use builders for entities/fields/keys
- make work for mariadb/mysql - make work for mariadb/mysql
- add database object to context with - add database object to context with

View File

@@ -1,6 +1,82 @@
package jef.model; 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 { 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 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));
}
}
} }

View File

@@ -179,17 +179,12 @@ public class DbEntity<T extends SerializableObject> {
return this.indexes.remove(index); return this.indexes.remove(index);
} }
private <R> String extractFieldName(SerializableFunction<T, R> getter) { /**
try { * Does not validate foreign key integrity
var expr = new AsmParser(getter).parse(); * @param primaryKey
if (expr.getType() != Expression.Type.INTERMEDIATE_FIELD) { */
throw new RuntimeException(expr.getClass().getSimpleName() + " is not a field expression"); public void setPrimaryKey(PrimaryKeyConstraint primaryKey) {
} this.primaryKey = primaryKey;
var name = ((IntermediateFieldExpression) expr).getName();
return name;
} catch (AsmParseException e) {
throw new RuntimeException("Invalid expression", e);
}
} }
public void addForeignKey(ForeignKeyConstraint foreignKey) { 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 @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View File

@@ -56,6 +56,7 @@ public class ModelBuilder {
var instance = context.getDeclaredConstructor().newInstance();//TODO find constructor, //TODO optionally with config var instance = context.getDeclaredConstructor().newInstance();//TODO find constructor, //TODO optionally with config
instance.onModelCreate(mb); instance.onModelCreate(mb);
instance.onModelValidate(mb);
return mb; return mb;
} }

View File

@@ -33,7 +33,7 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class MigrationCreator { 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) { public Result createMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, String currentSnapshotJava) {
var result = new Result(); var result = new Result();
@@ -70,18 +70,6 @@ public class MigrationCreator {
} }
private Result generateMigration(ModelBuilder from, ModelBuilder to, String name, String packageName, Result result) { 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 //reduce model builders to changes
var mcd = new ModelChangeDetector(from, to).detect(); var mcd = new ModelChangeDetector(from, to).detect();
var fromReduced = mcd.getFrom(); var fromReduced = mcd.getFrom();