Skip to content

Commit

Permalink
Merge pull request #274 from hasathcharu/new-fixes-wip
Browse files Browse the repository at this point in the history
Add compiler validations for custom foreign key fields
  • Loading branch information
daneshk committed Mar 11, 2024
2 parents 02872be + ac75229 commit 561b857
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 26 deletions.
2 changes: 1 addition & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[ballerina]
dependencies-toml-version = "2"
distribution-version = "2201.9.0-20240216-054100-84e1bc71"
distribution-version = "2201.9.0-20240208-103300-0823dc95"

[[package]]
org = "ballerina"
Expand Down
5 changes: 2 additions & 3 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
- [Added validation support for new persist commands](https://github.com/ballerina-platform/ballerina-library/issues/5784)

### Added
- [Added compiler plugin validations for Postgresql as a datasource](https://github.com/ballerina-platform/ballerina-library/issues/5829)

### Changed
- [Added validation support for new persist commands](https://github.com/ballerina-platform/ballerina-library/issues/5784)
- [Changed the behavior of foreign key presence validation to account for Relation annotation](https://github.com/ballerina-platform/ballerina-library/issues/6068)

## [1.1.0] - 2023-06-30

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,6 @@ public void validateDifferentOwners() {
"(26:4,26:25)",
"(35:4,35:24)",
"(27:4,27:28)",

}
);
}
Expand All @@ -628,7 +627,8 @@ public void validateUseOfEscapeCharacters() {
PERSIST_422.getCode()
},
new String[]{
"the entity should not contain foreign key field 'locationBuildingCode' for relation 'Building'"
"the entity should not contain foreign key field 'locationBuildingCode' for relation " +
"'Building'",
},
new String[]{
"(18:4,18:33)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public final class Constants {
public static final String EMPTY_STRING = "";
public static final String ARRAY = "[]";
public static final String LS = System.lineSeparator();
public static final String SQL_RELATION_MAPPING_ANNOTATION_NAME = "sql:Relation";
public static final String ANNOTATION_REFS_FIELD = "refs";

private Constants() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ public enum DiagnosticsCodes {
PERSIST_406("PERSIST_406", "1-n relationship does not support nillable relation field", ERROR),

PERSIST_420("PERSIST_420", "many-to-many relation is not supported yet", ERROR),
PERSIST_422("PERSIST_422", "the entity should not contain foreign key field ''{0}'' for relation ''{1}''", ERROR),

PERSIST_422("PERSIST_422",
"the entity should not contain foreign key field ''{0}'' for relation ''{1}''",
ERROR),
PERSIST_501("PERSIST_501", "''{0}'' entity must have at least one identity readonly field", ERROR),
PERSIST_502("PERSIST_502", "an identity field cannot be nillable", ERROR),
PERSIST_503("PERSIST_503", "only ''int'', ''string'', ''float'', ''boolean'', ''decimal'' " +
"types are supported as identity fields, found ''{0}''", ERROR);
"types are supported as identity fields, found ''{0}''", ERROR),
;

private final String code;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package io.ballerina.stdlib.persist.compiler;

import io.ballerina.compiler.syntax.tree.AnnotationNode;
import io.ballerina.compiler.syntax.tree.ArrayTypeDescriptorNode;
import io.ballerina.compiler.syntax.tree.BuiltinSimpleNameReferenceNode;
import io.ballerina.compiler.syntax.tree.EnumDeclarationNode;
Expand Down Expand Up @@ -58,11 +59,13 @@
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static io.ballerina.stdlib.persist.compiler.Constants.ANNOTATION_REFS_FIELD;
import static io.ballerina.stdlib.persist.compiler.Constants.BallerinaTypes.BOOLEAN;
import static io.ballerina.stdlib.persist.compiler.Constants.BallerinaTypes.DECIMAL;
import static io.ballerina.stdlib.persist.compiler.Constants.BallerinaTypes.FLOAT;
Expand Down Expand Up @@ -104,6 +107,7 @@
import static io.ballerina.stdlib.persist.compiler.utils.Utils.getFieldName;
import static io.ballerina.stdlib.persist.compiler.utils.Utils.getTypeName;
import static io.ballerina.stdlib.persist.compiler.utils.Utils.hasCompilationErrors;
import static io.ballerina.stdlib.persist.compiler.utils.Utils.readStringArrayValueFromAnnotation;
import static io.ballerina.stdlib.persist.compiler.utils.Utils.stripEscapeCharacter;

/**
Expand Down Expand Up @@ -182,9 +186,10 @@ public void perform(SyntaxNodeAnalysisContext ctx) {
for (TypeDefinitionNode typeDefinitionNode : foundEntities) {
String entityName = stripEscapeCharacter(typeDefinitionNode.typeName().text().trim());
TypeDescriptorNode typeDescriptorNode = (TypeDescriptorNode) typeDefinitionNode.typeDescriptor();

List<AnnotationNode> annotations = typeDefinitionNode.metadata().map(
metadata -> metadata.annotations().stream().toList()).orElse(Collections.emptyList());
Entity entity = new Entity(entityName, typeDefinitionNode.typeName().location(),
((RecordTypeDescriptorNode) typeDescriptorNode));
((RecordTypeDescriptorNode) typeDescriptorNode), annotations);
validateEntityRecordProperties(entity);
validateEntityFields(entity, datastore);
validateIdentityFields(entity);
Expand All @@ -203,9 +208,8 @@ public void perform(SyntaxNodeAnalysisContext ctx) {
validateGroupedRelation(field, this.entities.get(field.getContainingEntity()), entity, entity);
}
}

entity.getDiagnostics().forEach((ctx::reportDiagnostic));
this.entities.put(entityName, entity);
entity.getDiagnostics().forEach(ctx::reportDiagnostic);
}
}

Expand Down Expand Up @@ -261,6 +265,10 @@ private void validateEntityFields(Entity entity, String datastore) {
continue;
}

List<AnnotationNode> annotations =
recordFieldNode.metadata().map(metadata ->
metadata.annotations().stream().toList()).orElse(Collections.emptyList());

if (fieldNames.contains(fieldName.toLowerCase(Locale.ROOT))) {
entity.reportDiagnostic(PERSIST_307.getCode(),
MessageFormat.format(PERSIST_307.getMessage(), fieldName), PERSIST_307.getSeverity(),
Expand Down Expand Up @@ -328,7 +336,8 @@ private void validateEntityFields(Entity entity, String datastore) {
? modulePrefix + ":" + identifier + "?"
: modulePrefix + ":" + identifier;
entity.reportDiagnostic(PERSIST_306.getCode(),
MessageFormat.format(PERSIST_306.getMessage(), modulePrefix + ":" + identifier),
MessageFormat.format(PERSIST_306.getMessage(),
modulePrefix + ":" + identifier),
PERSIST_306.getSeverity(), typeNode.location(),
List.of(new BNumericProperty(arrayStartOffset), new BNumericProperty(arrayLength),
new BStringProperty(fieldType)));
Expand Down Expand Up @@ -357,7 +366,7 @@ private void validateEntityFields(Entity entity, String datastore) {
entity.addRelationField(new RelationField(fieldName, typeName,
typeNode.location().textRange().endOffset(), isOptionalType, nullableStartOffset,
isArrayType, arrayStartOffset, arrayLength, recordFieldNode.location(),
entity.getEntityName()));
entity.getEntityName(), annotations));
} else {
if (this.enumTypes.contains(typeName)) {
typeName = Constants.BallerinaTypes.ENUM;
Expand Down Expand Up @@ -395,7 +404,7 @@ private void validateEntityFields(Entity entity, String datastore) {

if (isSimpleType) {
entity.addNonRelationField(new SimpleTypeField(fieldName, fieldType, isValidType,
isOptionalType, isArrayType, fieldNode.location(), typeNode.location()));
isOptionalType, isArrayType, fieldNode.location(), typeNode.location(), annotations));
}

}
Expand Down Expand Up @@ -832,11 +841,22 @@ private void validatePresenceOfForeignKey(RelationField ownerRelationField, Enti
owner.getNonRelationFields().stream().
filter(field -> field.getName().equals(foreignKey))
.findFirst()
.ifPresent(field ->
reportDiagnosticsEntity.reportDiagnostic(PERSIST_422.getCode(), MessageFormat.format(
PERSIST_422.getMessage(), foreignKey, referredEntity.getEntityName()),
PERSIST_422.getSeverity(), field.getNodeLocation()));
.ifPresent(field -> validateRelationAnnotationFieldName
(field, reportDiagnosticsEntity, foreignKey, referredEntity,
ownerRelationField));
}
}

private void validateRelationAnnotationFieldName(SimpleTypeField field, Entity reportDiagnosticsEntity,
String foreignKey, Entity referredEntity,
RelationField ownerRelationField) {
List<String> references = readStringArrayValueFromAnnotation
(ownerRelationField.getAnnotations(), Constants.SQL_RELATION_MAPPING_ANNOTATION_NAME,
ANNOTATION_REFS_FIELD);
if (references == null || !references.contains(field.getName())) {
reportDiagnosticsEntity.reportDiagnostic(PERSIST_422.getCode(), MessageFormat.format(
PERSIST_422.getMessage(), foreignKey, referredEntity.getEntityName()),
PERSIST_422.getSeverity(), field.getNodeLocation());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package io.ballerina.stdlib.persist.compiler.model;

import io.ballerina.compiler.syntax.tree.AnnotationNode;
import io.ballerina.compiler.syntax.tree.NodeLocation;
import io.ballerina.compiler.syntax.tree.RecordTypeDescriptorNode;
import io.ballerina.tools.diagnostics.Diagnostic;
Expand All @@ -27,6 +28,7 @@
import io.ballerina.tools.diagnostics.DiagnosticSeverity;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -44,11 +46,14 @@ public class Entity {
private final HashMap<String, GroupedRelationField> groupedRelationFields = new HashMap<>();
private final List<Diagnostic> diagnosticList = new ArrayList<>();
private boolean containsRelations = false;
private final List<AnnotationNode> annotations;

public Entity(String entityName, NodeLocation entityNameLocation, RecordTypeDescriptorNode typeDescriptorNode) {
public Entity(String entityName, NodeLocation entityNameLocation, RecordTypeDescriptorNode typeDescriptorNode,
List<AnnotationNode> annotations) {
this.entityName = entityName;
this.entityNameLocation = entityNameLocation;
this.typeDescriptorNode = typeDescriptorNode;
this.annotations = annotations;
}

public String getEntityName() {
Expand Down Expand Up @@ -134,5 +139,7 @@ public void reportDiagnostic(String code, String message, DiagnosticSeverity sev
DiagnosticInfo diagnosticInfo = new DiagnosticInfo(code, message, severity);
this.diagnosticList.add(DiagnosticFactory.createDiagnostic(diagnosticInfo, location, diagnosticProperties));
}

public List<AnnotationNode> getAnnotations() {
return Collections.unmodifiableList(annotations);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public class IdentityField {
private int nullableStartOffset = 0;
private boolean isValidType = false;
private NodeLocation typeLocation;

public IdentityField(String name) {
this.name = name;
}
Expand Down Expand Up @@ -87,4 +86,5 @@ public NodeLocation getTypeLocation() {
public void setTypeLocation(NodeLocation typeLocation) {
this.typeLocation = typeLocation;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@

package io.ballerina.stdlib.persist.compiler.model;

import io.ballerina.compiler.syntax.tree.AnnotationNode;
import io.ballerina.compiler.syntax.tree.NodeLocation;

import java.util.Collections;
import java.util.List;

/**
* Model class to hold relation field details.
*/
Expand All @@ -37,10 +41,11 @@ public class RelationField {
private boolean isOwnerIdentifiable = false;
private String owner = null;
private RelationType relationType;
private final List<AnnotationNode> annotations;

public RelationField(String name, String type, int typeEndOffset, boolean isOptionalType, int nullableStartOffset,
boolean isArrayType, int arrayStartOffset, int arrayRangeLength, NodeLocation location,
String containingEntity) {
String containingEntity, List<AnnotationNode> annotations) {
this.name = name;
this.type = type;
this.typeEndOffset = typeEndOffset;
Expand All @@ -51,6 +56,7 @@ public RelationField(String name, String type, int typeEndOffset, boolean isOpti
this.arrayRangeLength = arrayRangeLength;
this.location = location;
this.containingEntity = containingEntity;
this.annotations = Collections.unmodifiableList(annotations);
}

public String getName() {
Expand Down Expand Up @@ -116,4 +122,9 @@ public RelationType getRelationType() {
public void setRelationType(RelationType relationType) {
this.relationType = relationType;
}

public List<AnnotationNode> getAnnotations() {
return annotations;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@

package io.ballerina.stdlib.persist.compiler.model;

import io.ballerina.compiler.syntax.tree.AnnotationNode;
import io.ballerina.compiler.syntax.tree.NodeLocation;

import java.util.Collections;
import java.util.List;

/**
* Simple type field model.
*/
Expand All @@ -32,16 +36,19 @@ public class SimpleTypeField {
private final boolean isArrayType;
private final NodeLocation nodeLocation;
private final NodeLocation typeLocation;
private final List<AnnotationNode> annotations;

public SimpleTypeField(String name, String type, boolean isValidType, boolean isNullable,
boolean isArrayType, NodeLocation location, NodeLocation typeLocation) {
boolean isArrayType, NodeLocation location, NodeLocation typeLocation,
List<AnnotationNode> annotations) {
this.name = name;
this.type = type;
this.isValidType = isValidType;
this.isNullable = isNullable;
this.isArrayType = isArrayType;
this.nodeLocation = location;
this.typeLocation = typeLocation;
this.annotations = Collections.unmodifiableList(annotations);
}

public String getName() {
Expand Down Expand Up @@ -71,4 +78,9 @@ public NodeLocation getNodeLocation() {
public NodeLocation getTypeLocation() {
return typeLocation;
}

public List<AnnotationNode> getAnnotations() {
return annotations;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@

package io.ballerina.stdlib.persist.compiler.utils;

import io.ballerina.compiler.syntax.tree.AnnotationNode;
import io.ballerina.compiler.syntax.tree.ExpressionNode;
import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode;
import io.ballerina.compiler.syntax.tree.MappingFieldNode;
import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.compiler.syntax.tree.SpecificFieldNode;
import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext;
import io.ballerina.projects.plugins.codeaction.CodeActionArgument;
import io.ballerina.projects.plugins.codeaction.CodeActionContext;
Expand Down Expand Up @@ -46,8 +51,11 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Stream;

/**
* Class containing util functions.
Expand Down Expand Up @@ -243,4 +251,30 @@ public static String getDatastore(CodeActionContext ctx) throws BalException {
throw new BalException("error while reading persist configurations. " + e.getMessage());
}
}

public static List<String> readStringArrayValueFromAnnotation(List<AnnotationNode> annotationNodes,
String annotation, String field) {
for (AnnotationNode annotationNode : annotationNodes) {
String annotationName = annotationNode.annotReference().toSourceCode().trim();
if (annotationName.equals(annotation)) {
Optional<MappingConstructorExpressionNode> annotationFieldNode = annotationNode.annotValue();
if (annotationFieldNode.isPresent()) {
for (MappingFieldNode mappingFieldNode : annotationFieldNode.get().fields()) {
SpecificFieldNode specificFieldNode = (SpecificFieldNode) mappingFieldNode;
String fieldName = specificFieldNode.fieldName().toSourceCode().trim();
if (!fieldName.equals(field)) {
return Collections.emptyList();
}
Optional<ExpressionNode> valueExpr = specificFieldNode.valueExpr();
if (valueExpr.isPresent()) {
return Stream.of(valueExpr.get().toSourceCode().trim().replace("\"", "")
.replace("[", "")
.replace("]", "").split(",")).map(String::trim).toList();
}
}
}
}
}
return Collections.emptyList();
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ testngVersion=7.6.1
gsonVersion=2.10.1
ballerinaGradlePluginVersion=2.0.1

ballerinaLangVersion=2201.9.0-20240216-054100-84e1bc71
ballerinaLangVersion=2201.9.0-20240208-103300-0823dc95

# Direct Dependencies

Expand Down

0 comments on commit 561b857

Please sign in to comment.