com.sap.cds.impl.CqnValidatorImpl Maven / Gradle / Ivy
/************************************************************************
* © 2019-2023 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.impl;
import static com.sap.cds.ql.cqn.CqnElementRef.$KEY;
import static com.sap.cds.util.CdsModelUtils.element;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.Sets;
import com.sap.cds.CdsDataStoreConnector.Capabilities;
import com.sap.cds.impl.docstore.DocStoreUtils;
import com.sap.cds.ql.CdsDataException;
import com.sap.cds.ql.cqn.CqnAnalyzer;
import com.sap.cds.ql.cqn.CqnDelete;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnInsert;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnUpdate;
import com.sap.cds.ql.cqn.CqnUpsert;
import com.sap.cds.ql.cqn.CqnValidationException;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.impl.DraftAdapter;
import com.sap.cds.reflect.impl.reader.model.CdsConstants;
import com.sap.cds.util.CdsModelUtils;
import com.sap.cds.util.CqnStatementUtils;
public class CqnValidatorImpl implements CqnValidator {
protected final CdsModel cdsModel;
protected final CqnAnalyzer cqnAnalyzer;
protected final DocStoreUtils docStoreUtils;
public CqnValidatorImpl(CdsModel cdsModel) {
this.cdsModel = cdsModel;
this.cqnAnalyzer = CqnAnalyzer.create(() -> cdsModel);
this.docStoreUtils = new DocStoreUtils(cdsModel);
}
@Override
public void validate(CqnSelect query) {
validate(query, Capabilities.ALL);
}
@Override
public void validate(CqnSelect query, Capabilities capabilities) {
if (!capabilities.supportsViewsWithParameters() && query.from().isRef()) {
CdsEntity entity = CdsModelUtils.entity(cdsModel, query.from().asRef());
if (entity.isView() && entity.params().count() > 0) {
throw new UnsupportedOperationException("Parametrized views are not supported by this data store");
}
}
}
@Override
public void validate(CqnInsert insert) {
if (docStoreUtils.targetsDocStore(insert)) {
return; // no need to validate insert data for docstore collections
}
validateElements(cqnAnalyzer.analyze(insert.ref()).targetEntity(), insert.elements());
}
@Override
public void validate(CqnUpdate update) {
if (docStoreUtils.targetsDocStore(update)) {
assertThatAllEntriesHaveSameStructure(update);
return; // no need to validate update data for docstore collections
}
validateElements(cqnAnalyzer.analyze(update.ref()).targetEntity(), update.elements());
CdsStructuredType targetType = CqnStatementUtils.rowType(cdsModel, update.ref());
update.where().ifPresent(w -> w.accept(new VirtualElementValidator(targetType)));
}
@Override
public void validate(CqnUpsert upsert) {
CdsEntity entity = cqnAnalyzer.analyze(upsert.ref()).targetEntity();
Set keyNames = CdsModelUtils.concreteKeyNames(entity);
if (DraftUtils.isDraftEnabled(entity) && !DraftUtils.isDraftView(entity)) {
keyNames.remove(DraftAdapter.IS_ACTIVE_ENTITY);
}
if (!upsert.elements().collect(Collectors.toSet()).containsAll(keyNames)) {
Set missingKeys = Sets.difference(keyNames, upsert.elements().collect(Collectors.toSet()));
throw new CdsDataException("Missing key value for " + missingKeys);
}
}
private void assertThatAllEntriesHaveSameStructure(CqnUpdate update) {
Set elements = update.data().keySet();
if (!update.entries().stream().allMatch(e -> e.keySet().equals(elements))) {
String allElements = update.elements().collect(Collectors.joining(", ", "[", "]"));
throw new UnsupportedOperationException(
"Each bulk update entry must contain values for the same elements " + allElements);
}
}
private void validateElements(CdsEntity entity, Stream usedElements) {
if (entity.findAnnotation(CdsConstants.OPEN).isEmpty()) {
usedElements.forEach(element -> {
int firstDot = element.indexOf('.');
String rootElement = firstDot == -1 ? element : element.substring(0, firstDot);
if (!(entity.findElement(rootElement).isPresent() || entity.findElement(element).isPresent())) {
throw new CqnValidationException(
"Element '" + element + "' does not exist in entity '" + entity.getQualifiedName() + "'");
}
});
}
}
@Override
public void validate(CqnDelete delete) {
CdsStructuredType targetType = CqnStatementUtils.rowType(cdsModel, delete.ref());
delete.where().ifPresent(w -> w.accept(new VirtualElementValidator(targetType)));
}
private class VirtualElementValidator implements CqnVisitor {
private CdsStructuredType root;
public VirtualElementValidator(CdsStructuredType rowType) {
root = rowType;
}
@Override
public void visit(CqnElementRef ref) {
if ($KEY.equals(ref.lastSegment())
|| CdsModelUtils.isEtagPlaceholder(ref)
|| CdsModelUtils.isContextElementRef(ref)) {
return;
}
if (element(root, ref).isVirtual()) {
throw virtualElementException(ref.displayName());
}
}
}
public static RuntimeException virtualElementException(String element) {
return new CqnValidationException(
"Virtual element '%s' is not allowed in filter conditions".formatted(element));
}
}