diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/persist/compiler/CompilerPluginTest.java b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/persist/compiler/CompilerPluginTest.java index b28306c4..3acc396d 100644 --- a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/persist/compiler/CompilerPluginTest.java +++ b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/persist/compiler/CompilerPluginTest.java @@ -46,6 +46,7 @@ import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_306; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_307; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_308; +import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_309; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_401; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_402; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_403; @@ -688,6 +689,63 @@ public void validateEntityNamesCaseSensitivity() { ); } + @Test + public void validateFieldTypesForRedisDB() { + List diagnostics = getErrorDiagnostics("project_10", + "field-types.bal", 14); + testDiagnostic( + diagnostics, + new String[]{ + PERSIST_309.getCode(), + PERSIST_308.getCode(), + PERSIST_308.getCode(), + PERSIST_305.getCode(), + PERSIST_305.getCode(), + PERSIST_305.getCode(), + PERSIST_305.getCode(), + PERSIST_306.getCode(), + PERSIST_306.getCode(), + PERSIST_306.getCode(), + PERSIST_306.getCode(), + PERSIST_306.getCode(), + PERSIST_306.getCode(), + PERSIST_306.getCode() + }, + new String[]{ + "an entity does not support optional readonly field", + "an entity does not support nillable field", + "an entity does not support nillable field", + "an entity does not support redis:Client-typed field", + "an entity does not support json-typed field", + "an entity does not support error-typed field", + "an entity does not support union-typed field", + "an entity does not support enum array field type", + "an entity does not support byte array field type", + "an entity does not support boolean array field type", + "an entity does not support json array field type", + "an entity does not support time:Civil array field type", + "an entity does not support error array field type", + "an entity does not support redis:Client array field type" + }, + new String[]{ + "(11:4,11:34)", + "(18:4,18:11)", + "(20:4,20:14)", + "(25:4,25:16)", + "(26:4,26:8)", + "(27:4,27:9)", + "(30:4,30:21)", + "(32:4,32:12)", + "(33:4,33:10)", + "(34:4,34:13)", + "(35:4,35:10)", + "(36:4,36:16)", + "(37:4,37:11)", + "(38:4,38:18)" + } + ); + } + private List getErrorDiagnostics(String modelDirectory, String modelFileName, int count) { DiagnosticResult diagnosticResult = loadPersistModelFile(modelDirectory, modelFileName).getCompilation() .diagnosticResult(); diff --git a/compiler-plugin-test/src/test/resources/project_10/Ballerina.toml b/compiler-plugin-test/src/test/resources/project_10/Ballerina.toml new file mode 100644 index 00000000..42893a4b --- /dev/null +++ b/compiler-plugin-test/src/test/resources/project_10/Ballerina.toml @@ -0,0 +1,9 @@ +[package] +org = "root" +name = "project_10" +version = "0.1.0" + +[[tool.persist]] +id = "project10" +options.datastore = "redis" +filePath = "persist/model.bal" diff --git a/compiler-plugin-test/src/test/resources/project_10/persist/field-types.bal b/compiler-plugin-test/src/test/resources/project_10/persist/field-types.bal new file mode 100644 index 00000000..5c8a5828 --- /dev/null +++ b/compiler-plugin-test/src/test/resources/project_10/persist/field-types.bal @@ -0,0 +1,85 @@ +import ballerina/time; +import ballerinax/redis; +import ballerina/persist as _; + +public enum Gender { + M, + F +} + +public type MedicalNeed record {| + readonly int needId; + readonly string stringNeedId?; + + boolean booleanType; + int intType; + float floatType; + decimal decimalType; + string stringType; + string? stringNillableType; + time:Date dateType; + time:Date? dateNillableType; + time:TimeOfDay timeOfDayType; + time:Utc utcType; + time:Civil civilType; + Gender gender; + redis:Client clientType; + json jsonTest; + error errorType; + + boolean booleanTypeOptional?; + time:Civil|string unionType; + + Gender[] genderArray; + byte[] beneficiaryIdByteArray; + boolean[] booleanArray; + json[] jsonArray; + time:Civil[] periodArray; + error[] errorArrayType; + redis:Client[] clientArrayType; +|}; + +type Employee record {| + readonly string empNo; + string firstName; + string lastName; + time:Date birthDate; + Gender gender; + time:Date hireDate; + + Department department; + Workspace workspace; +|}; + +type Workspace record {| + readonly string workspaceId; + string workspaceType; + + Building location; + Employee[] employees; +|}; + +type Building record {| + readonly string buildingCode; + string city; + string state; + string country; + string postalCode; + string 'type; + + Workspace[] workspaces; +|}; + +type Department record {| + readonly string deptNo; + string deptName; + + Employee[] employees; +|}; + +type OrderItem record {| + readonly string orderId; + readonly string itemId; + int quantity; + string notes; +|}; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/Constants.java index d350432a..bd72ba14 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/Constants.java @@ -32,7 +32,7 @@ public final class Constants { 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"; + public static final String ANNOTATION_KEYS_FIELD = "keys"; private Constants() { } @@ -78,6 +78,7 @@ public static final class Datastores { public static final String POSTGRESQL = "postgresql"; public static final String IN_MEMORY = "inmemory"; public static final String GOOGLE_SHEETS = "googlesheets"; + public static final String REDIS = "redis"; private Datastores() { } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/DiagnosticsCodes.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/DiagnosticsCodes.java index 4788bbc8..d0e82b56 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/DiagnosticsCodes.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/DiagnosticsCodes.java @@ -54,6 +54,7 @@ public enum DiagnosticsCodes { PERSIST_306("PERSIST_306", "an entity does not support {0} array field type", ERROR), PERSIST_307("PERSIST_307", "redeclared field ''{0}''", ERROR), PERSIST_308("PERSIST_308", "an entity does not support nillable field", ERROR), + PERSIST_309("PERSIST_309", "an entity does not support optional readonly field", ERROR), PERSIST_401("PERSIST_401", "an entity cannot reference itself in a relation field", ERROR), diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/PersistModelDefinitionValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/PersistModelDefinitionValidator.java index 2ea3de93..ee7e1619 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/PersistModelDefinitionValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/PersistModelDefinitionValidator.java @@ -65,7 +65,7 @@ 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.ANNOTATION_KEYS_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; @@ -89,6 +89,7 @@ import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_305; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_306; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_307; +import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_309; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_401; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_402; import static io.ballerina.stdlib.persist.compiler.DiagnosticsCodes.PERSIST_403; @@ -279,11 +280,21 @@ private void validateEntityFields(Entity entity, String datastore) { // Check if optional field if (recordFieldNode.questionMarkToken().isPresent()) { - int startOffset = recordFieldNode.questionMarkToken().get().textRange().startOffset(); - int length = recordFieldNode.semicolonToken().textRange().startOffset() - startOffset; - entity.reportDiagnostic(PERSIST_304.getCode(), PERSIST_304.getMessage(), PERSIST_304.getSeverity(), - recordFieldNode.location(), - List.of(new BNumericProperty(startOffset), new BNumericProperty(length))); + if (datastore.equals(Constants.Datastores.REDIS)) { + if (recordFieldNode.readonlyKeyword().isPresent()) { + int startOffset = recordFieldNode.questionMarkToken().get().textRange().startOffset(); + int length = recordFieldNode.semicolonToken().textRange().startOffset() - startOffset; + entity.reportDiagnostic(PERSIST_309.getCode(), PERSIST_309.getMessage(), + PERSIST_309.getSeverity(), recordFieldNode.location(), + List.of(new BNumericProperty(startOffset), new BNumericProperty(length))); + } + } else { + int startOffset = recordFieldNode.questionMarkToken().get().textRange().startOffset(); + int length = recordFieldNode.semicolonToken().textRange().startOffset() - startOffset; + entity.reportDiagnostic(PERSIST_304.getCode(), PERSIST_304.getMessage(), PERSIST_304.getSeverity(), + recordFieldNode.location(), + List.of(new BNumericProperty(startOffset), new BNumericProperty(length))); + } } Node typeNode = recordFieldNode.typeName(); @@ -330,31 +341,13 @@ private void validateEntityFields(Entity entity, String datastore) { String modulePrefix = stripEscapeCharacter(qualifiedName.modulePrefix().text()); String identifier = stripEscapeCharacter(qualifiedName.identifier().text()); fieldType = modulePrefix + ":" + identifier; - if (ValidatorsByDatastore.isValidImportedType(modulePrefix, identifier, datastore)) { - if (isArrayType && !ValidatorsByDatastore.isValidArrayType(fieldType, datastore)) { - fieldType = isOptionalType - ? modulePrefix + ":" + identifier + "?" - : modulePrefix + ":" + identifier; - entity.reportDiagnostic(PERSIST_306.getCode(), - MessageFormat.format(PERSIST_306.getMessage(), - modulePrefix + ":" + identifier), - PERSIST_306.getSeverity(), typeNode.location(), - List.of(new BNumericProperty(arrayStartOffset), new BNumericProperty(arrayLength), - new BStringProperty(fieldType))); - } else { - isValidType = true; - } - } else { - if (isArrayType) { - entity.reportDiagnostic(PERSIST_306.getCode(), MessageFormat.format(PERSIST_306.getMessage(), - modulePrefix + ":" + identifier), PERSIST_305.getSeverity(), - typeNode.location()); - } else { - entity.reportDiagnostic(PERSIST_305.getCode(), MessageFormat.format(PERSIST_305.getMessage(), - modulePrefix + ":" + identifier), PERSIST_305.getSeverity(), - typeNode.location()); - } - } + List> properties = List.of( + new BNumericProperty(arrayStartOffset), + new BNumericProperty(arrayLength), + new BStringProperty(isOptionalType ? fieldType + "?" : fieldType)); + + isValidType = ValidatorsByDatastore.validateImportedTypes( + entity, typeNode, isArrayType, isOptionalType, properties, modulePrefix, identifier, datastore); isSimpleType = true; } else if (processedTypeNode instanceof SimpleNameReferenceNode) { String typeName = stripEscapeCharacter( @@ -852,7 +845,7 @@ private void validateRelationAnnotationFieldName(SimpleTypeField field, Entity r RelationField ownerRelationField) { List references = readStringArrayValueFromAnnotation (ownerRelationField.getAnnotations(), Constants.SQL_RELATION_MAPPING_ANNOTATION_NAME, - ANNOTATION_REFS_FIELD); + ANNOTATION_KEYS_FIELD); if (references == null || !references.contains(field.getName())) { reportDiagnosticsEntity.reportDiagnostic(PERSIST_422.getCode(), MessageFormat.format( PERSIST_422.getMessage(), foreignKey, referredEntity.getEntityName()), diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/utils/ValidatorsByDatastore.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/utils/ValidatorsByDatastore.java index 811856ca..95290c41 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/utils/ValidatorsByDatastore.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/persist/compiler/utils/ValidatorsByDatastore.java @@ -55,7 +55,8 @@ public static boolean validateSimpleTypes(Entity entity, Node typeNode, String t List> properties, String type, String datastore) { boolean validFlag = true; - if (isOptionalType && Constants.Datastores.GOOGLE_SHEETS.equals(datastore)) { + if (isOptionalType && (Constants.Datastores.GOOGLE_SHEETS.equals(datastore) + || Constants.Datastores.REDIS.equals(datastore))) { entity.reportDiagnostic(PERSIST_308.getCode(), MessageFormat.format(PERSIST_308.getMessage(), type), PERSIST_308.getSeverity(), typeNode.location(), properties); @@ -86,8 +87,47 @@ public static boolean validateSimpleTypes(Entity entity, Node typeNode, String t return validFlag; } + public static boolean validateImportedTypes(Entity entity, Node typeNode, + boolean isArrayType, boolean isOptionalType, + List> properties, + String modulePrefix, String identifier, String datastore) { + boolean validFlag = true; + + if (isOptionalType && datastore.equals(Constants.Datastores.REDIS)) { + entity.reportDiagnostic(PERSIST_308.getCode(), + MessageFormat.format(PERSIST_308.getMessage(), modulePrefix + ":" + identifier), + PERSIST_308.getSeverity(), typeNode.location(), properties); + validFlag = false; + } + + if (ValidatorsByDatastore.isValidImportedType(modulePrefix, identifier, datastore)) { + if (isArrayType && !ValidatorsByDatastore.isValidArrayType(modulePrefix + ":" + identifier, + datastore)) { + + entity.reportDiagnostic(PERSIST_306.getCode(), + MessageFormat.format(PERSIST_306.getMessage(), modulePrefix + ":" + identifier), + PERSIST_306.getSeverity(), typeNode.location(), + properties); + } else { + validFlag = true; + } + } else { + if (isArrayType) { + entity.reportDiagnostic(PERSIST_306.getCode(), MessageFormat.format(PERSIST_306.getMessage(), + modulePrefix + ":" + identifier), PERSIST_305.getSeverity(), + typeNode.location()); + } else { + entity.reportDiagnostic(PERSIST_305.getCode(), MessageFormat.format(PERSIST_305.getMessage(), + modulePrefix + ":" + identifier), PERSIST_305.getSeverity(), + typeNode.location()); + } + } + + return validFlag; + } + public static boolean isValidSimpleType(String type, String datastore) { - // If the datastore is null(ex: generate command), ignore the data type validation. + // Ignore the validation if the datastore is not specified in toml files(ex:before executing generate command). if (null == datastore) { return true; } @@ -102,13 +142,15 @@ public static boolean isValidSimpleType(String type, String datastore) { return isValidInMemoryType(type); case Constants.Datastores.GOOGLE_SHEETS: return isValidGoogleSheetsType(type); + case Constants.Datastores.REDIS: + return isValidRedisType(type); default: return false; } } public static boolean isValidArrayType(String type, String datastore) { - // If the datastore is null(ex: generate command), ignore the data type validation. + // Ignore the validation if the datastore is not specified in toml files(ex:before executing generate command). if (null == datastore) { return true; } @@ -123,13 +165,15 @@ public static boolean isValidArrayType(String type, String datastore) { return isValidInMemoryArrayType(type); case Constants.Datastores.GOOGLE_SHEETS: return isValidGoogleSheetsArrayType(type); + case Constants.Datastores.REDIS: + return isValidRedisArrayType(type); default: return false; } } public static boolean isValidImportedType(String modulePrefix, String identifier, String datastore) { - // If the datastore is null(ex: generate command), ignore the data type validation. + // Ignore the validation if the datastore is not specified in toml files(ex:before executing generate command). if (null == datastore) { return true; } @@ -144,6 +188,8 @@ public static boolean isValidImportedType(String modulePrefix, String identifier return isValidInMemoryImportedType(modulePrefix, identifier); case Constants.Datastores.GOOGLE_SHEETS: return isValidGoogleSheetsImportedType(modulePrefix, identifier); + case Constants.Datastores.REDIS: + return isValidRedisImportedType(modulePrefix, identifier); default: return false; } @@ -209,6 +255,20 @@ public static boolean isValidGoogleSheetsType(String type) { } } + public static boolean isValidRedisType(String type) { + switch (type) { + case INT: + case BOOLEAN: + case DECIMAL: + case FLOAT: + case STRING: + case ENUM: + return true; + default: + return false; + } + } + public static boolean isValidMysqlArrayType(String type) { switch (type) { case BYTE: @@ -244,6 +304,10 @@ public static boolean isValidGoogleSheetsArrayType(String type) { return false; } + public static boolean isValidRedisArrayType(String type) { + return false; + } + public static boolean isValidMysqlImportedType(String modulePrefix, String identifier) { if (!modulePrefix.equals(TIME_MODULE)) { return false; @@ -308,4 +372,19 @@ public static boolean isValidGoogleSheetsImportedType(String modulePrefix, Strin } } + public static boolean isValidRedisImportedType(String modulePrefix, String identifier) { + if (!modulePrefix.equals(TIME_MODULE)) { + return false; + } + switch (identifier) { + case DATE: + case TIME_OF_DAY: + case CIVIL: + case UTC: + return true; + default: + return false; + } + } + }