added basic model validation
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user