Skip to content

Commit

Permalink
feat: support for constraint on edges out/in + LINK, EMBEDDED, LIST a…
Browse files Browse the repository at this point in the history
…nd MAP types

Fixed issue #1174
  • Loading branch information
lvca committed Jul 18, 2023
1 parent f3006e1 commit 8878ad3
Show file tree
Hide file tree
Showing 15 changed files with 4,507 additions and 4,149 deletions.
1 change: 1 addition & 0 deletions console/src/main/java/com/arcadedb/console/Console.java
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,7 @@ else if (subject.startsWith("type ")) {
final TableFormatter.TableMapRow row = new TableFormatter.TableMapRow();
row.setField("NAME", property.getProperty("name"));
row.setField("TYPE", property.getProperty("type"));
row.setField("OF", property.hasProperty("of") ? property.getProperty("of") : null);
row.setField("MANDATORY", property.hasProperty("mandatory") ? property.getProperty("mandatory") : "false");
row.setField("READONLY", property.hasProperty("readOnly") ? property.getProperty("readOnly") : "false");
row.setField("NOT NULL", property.hasProperty("notNull") ? property.getProperty("notNull") : "false");
Expand Down
485 changes: 245 additions & 240 deletions engine/src/main/grammar/SQLGrammar.jjt

Large diffs are not rendered by default.

86 changes: 77 additions & 9 deletions engine/src/main/java/com/arcadedb/database/DocumentValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@
*/
package com.arcadedb.database;

import com.arcadedb.exception.ValidationException;
import com.arcadedb.schema.Property;
import com.arcadedb.schema.Type;
import com.arcadedb.exception.*;
import com.arcadedb.schema.*;

import java.math.*;
import java.util.*;
Expand Down Expand Up @@ -56,31 +55,100 @@ public static void validateField(final MutableDocument document, final Property
final Type propertyType = p.getType();

if (propertyType != null) {
final String ofType = p.getOfType();

// CHECK EMBEDDED VALUES
switch (propertyType) {
case EMBEDDED:
case LINK: {
if (fieldValue instanceof EmbeddedDocument)
throwValidationException(p, "has been declared as LINK but an EMBEDDED document is used. Value: " + fieldValue);

if (ofType != null) {
final RID rid = ((Identifiable) fieldValue).getIdentity();
final DocumentType embSchemaType = document.getDatabase().getSchema().getTypeByBucketId(rid.getBucketId());
if (!embSchemaType.instanceOf(ofType))
throwValidationException(p, "has been declared as LINK of '" + ofType + "' but a link to type '" + embSchemaType + "' is used. Value: " + fieldValue);
}
}
break;

case EMBEDDED: {
if (!(fieldValue instanceof EmbeddedDocument))
throwValidationException(p, "has been declared as EMBEDDED but an incompatible type is used. Value: " + fieldValue);

if (ofType != null) {
final DocumentType embSchemaType = ((EmbeddedDocument) fieldValue).getType();
if (!embSchemaType.instanceOf(ofType))
throwValidationException(p,
"has been declared as EMBEDDED of '" + ofType + "' but a document of type '" + embSchemaType + "' is used. Value: " + fieldValue);
}
if (fieldValue instanceof MutableEmbeddedDocument)
((MutableEmbeddedDocument) fieldValue).validate();
break;
case LIST:
}
break;

case LIST: {
if (!(fieldValue instanceof List))
throwValidationException(p, "has been declared as LIST but an incompatible type is used. Value: " + fieldValue);

final Type embType = ofType != null ? Type.getTypeByName(ofType) : null;

for (final Object item : ((List<?>) fieldValue)) {
if (ofType != null) {
if (embType != null) {
if (Type.getTypeByValue(item) != embType)
throwValidationException(p,
"has been declared as LIST of '" + ofType + "' but a value of type '" + Type.getTypeByValue(item) + "' is used. Value: " + fieldValue);
} else if (item instanceof EmbeddedDocument) {
if (!((EmbeddedDocument) item).getType().instanceOf(ofType))
throwValidationException(p,
"has been declared as LIST of '" + ofType + "' but an embedded document of type '" + embType + "' is used. Value: " + fieldValue);
} else if (item instanceof Identifiable) {
final RID rid = ((Identifiable) item).getIdentity();
final DocumentType embSchemaType = document.getDatabase().getSchema().getTypeByBucketId(rid.getBucketId());
if (!embSchemaType.instanceOf(ofType))
throwValidationException(p,
"has been declared as LIST of '" + ofType + "' but a link to type '" + embSchemaType + "' is used. Value: " + fieldValue);
}
}

if (item instanceof MutableEmbeddedDocument)
((MutableEmbeddedDocument) item).validate();
}
break;
}
break;

case MAP:
case MAP: {
if (!(fieldValue instanceof Map))
throwValidationException(p, "has been declared as MAP but an incompatible type is used. Value: " + fieldValue);

final Type embType = ofType != null ? Type.getTypeByName(ofType) : null;

for (final Object item : ((Map<?, ?>) fieldValue).values()) {
if (ofType != null) {
if (embType != null) {
if (Type.getTypeByValue(item) != embType)
throwValidationException(p,
"has been declared as MAP of <String,'" + ofType + "'> but a value of type '" + Type.getTypeByValue(item) + "' is used. Value: "
+ fieldValue);
} else if (item instanceof EmbeddedDocument) {
if (!((EmbeddedDocument) item).getType().instanceOf(ofType))
throwValidationException(p,
"has been declared as MAP of <String,'" + ofType + "'> but an embedded document of type '" + embType + "' is used. Value: " + fieldValue);
} else if (item instanceof Identifiable) {
final RID rid = ((Identifiable) item).getIdentity();
final DocumentType embSchemaType = document.getDatabase().getSchema().getTypeByBucketId(rid.getBucketId());
if (!embSchemaType.instanceOf(ofType))
throwValidationException(p,
"has been declared as LIST of '" + ofType + "' but a link to type '" + embType + "' is used. Value: " + fieldValue);
}
}

if (item instanceof MutableEmbeddedDocument)
((MutableEmbeddedDocument) item).validate();
}
break;
}
break;
}
}

Expand Down
15 changes: 14 additions & 1 deletion engine/src/main/java/com/arcadedb/database/MutableDocument.java
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ protected Object convertValueToSchemaType(final String name, final Object value,
if (property != null)
try {
final Type propType = property.getType();
final String ofType = property.getOfType();

final Class javaImplementation;
if (propType == Type.DATE)
Expand All @@ -400,6 +401,18 @@ else if (propType == Type.DATETIME)
if (value instanceof Map) {
final Map<String, Object> map = (Map<String, Object>) value;
final String embType = (String) map.get("@type");

if (ofType != null) {
// VALIDATE CONSTRAINT
if (!ofType.equals(embType)) {
// CHECK INHERITANCE
final DocumentType schemaType = database.getSchema().getType(embType);
if (!schemaType.instanceOf(ofType))
throw new ValidationException(
"Embedded type '" + embType + "' is not compatible with the type defined in the schema constraint '" + ofType + "'");
}
}

return newEmbeddedDocument(embType, name);
}
}
Expand All @@ -415,9 +428,9 @@ else if (propType == Type.DATETIME)
}

private Object setTransformValue(final Object value, final String propertyName) {
// SET DIRTY TO FORCE RE-MARSHALL. IF THE RECORD COMES FROM ANOTHER DATABASE WITHOUT A FULL RE-MARSHALL, IT WILL HAVE THE DICTIONARY IDS OF THE OTHER DATABASE
if (value instanceof EmbeddedDocument) {
if (!((EmbeddedDocument) value).getDatabase().getName().equals(database.getName())) {
// SET DIRTY TO FORCE RE-MARSHALL. IF THE RECORD COMES FROM ANOTHER DATABASE WITHOUT A FULL RE-MARSHALL, IT WILL HAVE THE DICTIONARY IDS OF THE OTHER DATABASE
((BaseDocument) value).buffer.rewind();
final MutableDocument newRecord = (MutableDocument) database.getRecordFactory()
.newMutableRecord(database, ((EmbeddedDocument) value).getType(), null, ((BaseDocument) value).buffer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ else if (type.getType() == Edge.RECORD_TYPE)
propRes.setProperty("name", property.getName());
propRes.setProperty("type", property.getType());

if (property.getOfType() != null)
propRes.setProperty("ofType", property.getOfType());
if (property.isMandatory())
propRes.setProperty("mandatory", property.isMandatory());
if (property.isReadonly())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class CreatePropertyStatement extends DDLStatement {
public Identifier typeName;
public Identifier propertyName;
public Identifier propertyType;
public Identifier ofType;
public List<CreatePropertyAttributeStatement> attributes = new ArrayList<CreatePropertyAttributeStatement>();
boolean ifNotExists = false;

Expand All @@ -50,6 +51,8 @@ public ResultSet executeDDL(final CommandContext context) {
result.setProperty("operation", "create property");
result.setProperty("typeName", typeName.getStringValue());
result.setProperty("propertyName", propertyName.getStringValue());
if (ofType != null)
result.setProperty("of", ofType.getStringValue());
executeInternal(context, result);
final InternalResultSet rs = new InternalResultSet();
rs.add(result);
Expand All @@ -60,19 +63,20 @@ private void executeInternal(final CommandContext context, final ResultInternal
final Database db = context.getDatabase();
final DocumentType typez = db.getSchema().getType(typeName.getStringValue());
if (typez == null)
throw new CommandExecutionException("Type not found: " + typeName.getStringValue());
throw new CommandExecutionException("Type '" + typeName.getStringValue() + "' not found");

if (typez.existsProperty(propertyName.getStringValue())) {
if (ifNotExists)
return;

throw new CommandExecutionException("Property " + typeName.getStringValue() + "." + propertyName.getStringValue() + " already exists");
throw new CommandExecutionException("Property '" + typeName.getStringValue() + "." + propertyName.getStringValue() + "' already exists");
}

final Type type = Type.valueOf(propertyType.getStringValue().toUpperCase(Locale.ENGLISH));

// CREATE IT LOCALLY
final Property internalProp = typez.createProperty(propertyName.getStringValue(), type);
final String ofTypeAsString = ofType != null ? ofType.getStringValue() : null;
final Property internalProp = typez.createProperty(propertyName.getStringValue(), type, ofTypeAsString);
for (final CreatePropertyAttributeStatement attr : attributes) {
final Object val = attr.setOnProperty(internalProp, context);
result.setProperty(attr.settingName.getStringValue(), val);
Expand All @@ -91,6 +95,11 @@ public void toString(final Map<String, Object> params, final StringBuilder build
builder.append(" ");
propertyType.toString(params, builder);

if (ofType != null) {
builder.append(" OF ");
ofType.toString(params, builder);
}

if (!attributes.isEmpty()) {
builder.append(" (");
for (int i = 0; i < attributes.size(); i++) {
Expand All @@ -110,8 +119,9 @@ public CreatePropertyStatement copy() {
final CreatePropertyStatement result = new CreatePropertyStatement(-1);
result.typeName = typeName == null ? null : typeName.copy();
result.propertyName = propertyName == null ? null : propertyName.copy();
result.propertyType = propertyType == null ? null : propertyType.copy();
result.ifNotExists = ifNotExists;
result.propertyType = propertyType == null ? null : propertyType.copy();
result.ofType = ofType == null ? null : ofType.copy();
result.attributes = attributes == null ? null : attributes.stream().map(x -> x.copy()).collect(Collectors.toList());
return result;
}
Expand All @@ -131,6 +141,8 @@ public boolean equals(final Object o) {
return false;
if (!Objects.equals(propertyType, that.propertyType))
return false;
if (!Objects.equals(ofType, that.ofType))
return false;
if (!Objects.equals(attributes, that.attributes))
return false;
return ifNotExists == that.ifNotExists;
Expand All @@ -141,6 +153,7 @@ public int hashCode() {
int result = typeName != null ? typeName.hashCode() : 0;
result = 31 * result + (propertyName != null ? propertyName.hashCode() : 0);
result = 31 * result + (propertyType != null ? propertyType.hashCode() : 0);
result = 31 * result + (ofType != null ? ofType.hashCode() : 0);
result = 31 * result + (attributes != null ? attributes.hashCode() : 0);
return result;
}
Expand Down
Loading

0 comments on commit 8878ad3

Please sign in to comment.