private static void validateProperty()

in core/src/main/java/com/jetbrains/youtrackdb/internal/core/record/impl/EntityImpl.java [1579:1857]


  private static void validateProperty(
      DatabaseSessionInternal session, ImmutableSchema schema, EntityImpl iRecord,
      ImmutableSchemaProperty p)
      throws ValidationException {
    iRecord.checkForBinding();

    final Object propertyValue;
    var entry = iRecord.properties.get(p.getName());
    if (entry != null && entry.exists()) {
      // AVOID CONVERSIONS: FASTER!
      propertyValue = entry.value;

      if (p.isNotNull() && propertyValue == null)
      // NULLITY
      {
        throw new ValidationException(session.getDatabaseName(),
            "The property '" + p.getFullName() + "' cannot be null, record: " + iRecord);
      }

      if (propertyValue != null && p.getRegexp() != null && p.getType() == PropertyType.STRING) {
        // REGEXP
        if (!((String) propertyValue).matches(p.getRegexp())) {
          throw new ValidationException(session.getDatabaseName(),
              "The property '"
                  + p.getFullName()
                  + "' does not match the regular expression '"
                  + p.getRegexp()
                  + "'. Field value is: "
                  + propertyValue
                  + ", record: "
                  + iRecord);
        }
      }

    } else {
      if (p.isMandatory()) {
        throw new ValidationException(session.getDatabaseName(),
            "The property '"
                + p.getFullName()
                + "' is mandatory, but not found on record: "
                + iRecord);
      }
      propertyValue = null;
    }

    final var type = p.getType();

    if (propertyValue != null && type != null) {
      // CHECK TYPE
      switch (type) {
        case LINK:
          validateLink(schema, session, p, propertyValue, false);
          break;
        case LINKLIST:
          if (!(propertyValue instanceof EntityLinkListImpl)) {
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' has been declared as LINKLIST but an incompatible type is used. Value: "
                    + propertyValue);
          }
          validateLinkCollection(session, schema, p, (Collection<Object>) propertyValue, entry);
          break;
        case LINKSET:
          if (!(propertyValue instanceof EntityLinkSetImpl)) {
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' has been declared as LINKSET but an incompatible type is used. Value: "
                    + propertyValue);
          }
          validateLinkCollection(session, schema, p, (Collection<Object>) propertyValue, entry);
          break;
        case LINKMAP:
          if (!(propertyValue instanceof EntityLinkMapIml)) {
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' has been declared as LINKMAP but an incompatible type is used. Value: "
                    + propertyValue);
          }
          validateLinkCollection(session, schema, p, ((Map<?, Object>) propertyValue).values(),
              entry);
          break;

        case LINKBAG:
          if (!(propertyValue instanceof LinkBag)) {
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' has been declared as LINKBAG but an incompatible type is used. Value: "
                    + propertyValue);
          }
          validateLinkCollection(session, schema, p, (Iterable<Object>) propertyValue, entry);
          break;
        case EMBEDDED:
          validateEmbedded(session, p, propertyValue);
          break;
        case EMBEDDEDLIST:
          if (!(propertyValue instanceof EntityEmbeddedListImpl<?>)) {
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' has been declared as EMBEDDEDLIST but an incompatible type is used. Value:"
                    + " "
                    + propertyValue);
          }
          if (p.getLinkedClass() != null) {
            for (var item : ((List<?>) propertyValue)) {
              validateEmbedded(session, p, item);
            }
          } else {
            if (p.getLinkedType() != null) {
              for (var item : ((List<?>) propertyValue)) {
                validateType(session, p, item);
              }
            }
          }
          break;
        case EMBEDDEDSET:
          if (!(propertyValue instanceof EntityEmbeddedSetImpl<?>)) {
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' has been declared as EMBEDDEDSET but an incompatible type is used. Value: "
                    + propertyValue);
          }
          if (p.getLinkedClass() != null) {
            for (var item : ((Set<?>) propertyValue)) {
              validateEmbedded(session, p, item);
            }
          } else {
            if (p.getLinkedType() != null) {
              for (var item : ((Set<?>) propertyValue)) {
                validateType(session, p, item);
              }
            }
          }
          break;
        case EMBEDDEDMAP:
          if (!(propertyValue instanceof EntityEmbeddedMapImpl<?>)) {
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' has been declared as EMBEDDEDMAP but an incompatible type is used. Value: "
                    + propertyValue);
          }
          if (p.getLinkedClass() != null) {
            for (var colleEntry : ((Map<?, ?>) propertyValue).entrySet()) {
              validateEmbedded(session, p, colleEntry.getValue());
            }
          } else {
            if (p.getLinkedType() != null) {
              for (var collEntry : ((Map<?, ?>) propertyValue).entrySet()) {
                validateType(session, p, collEntry.getValue());
              }
            }
          }
          break;
      }
    }

    if (p.getMin() != null && propertyValue != null) {
      // MIN
      final var min = p.getMin();
      if (p.getMinComparable().compareTo(propertyValue) > 0) {
        switch (p.getType()) {
          case STRING:
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' contains fewer characters than "
                    + min
                    + " requested");
          case DATE:
          case DATETIME:
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' contains the date "
                    + propertyValue
                    + " which precedes the first acceptable date ("
                    + min
                    + ")");
          case BINARY:
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' contains fewer bytes than "
                    + min
                    + " requested");
          case EMBEDDEDLIST:
          case EMBEDDEDSET:
          case LINKLIST:
          case LINKSET:
          case EMBEDDEDMAP:
          case LINKMAP:
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' contains fewer items than "
                    + min
                    + " requested");
          default:
            throw new ValidationException(session.getDatabaseName(),
                "The property '" + p.getFullName() + "' is less than " + min);
        }
      }
    }

    if (p.getMaxComparable() != null && propertyValue != null) {
      final var max = p.getMax();
      if (p.getMaxComparable().compareTo(propertyValue) < 0) {
        switch (p.getType()) {
          case STRING:
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' contains more characters than "
                    + max
                    + " requested");
          case DATE:
          case DATETIME:
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' contains the date "
                    + propertyValue
                    + " which is after the last acceptable date ("
                    + max
                    + ")");
          case BINARY:
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' contains more bytes than "
                    + max
                    + " requested");
          case EMBEDDEDLIST:
          case EMBEDDEDSET:
          case LINKLIST:
          case LINKSET:
          case EMBEDDEDMAP:
          case LINKMAP:
            throw new ValidationException(session.getDatabaseName(),
                "The property '"
                    + p.getFullName()
                    + "' contains more items than "
                    + max
                    + " requested");
          default:
            throw new ValidationException(session.getDatabaseName(),
                "The property '" + p.getFullName() + "' is greater than " + max);
        }
      }
    }

    if (p.isReadonly()) {
      if (entry != null
          && (entry.isTxChanged() || entry.isTxTrackedModified())
          && !entry.isTxCreated()) {
        // check if the property is actually changed by equal.
        // this is due to a limitation in the merge algorithm used server side marking all
        // non-simple properties as dirty
        var orgVal = entry.getOnLoadValue(session);
        var simple =
            propertyValue != null ? PropertyTypeInternal.isSimpleValueType(propertyValue)
                : PropertyTypeInternal.isSimpleValueType(orgVal);
        if (simple || propertyValue != null && orgVal == null || propertyValue == null
            || !Objects.deepEquals(propertyValue, orgVal)) {
          throw new ValidationException(session.getDatabaseName(),
              "The property '"
                  + p.getFullName()
                  + "' is immutable and cannot be altered. Field value is: "
                  + entry.value);
        }
      }
    }
  }