All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.impl.CqnValidatorImpl Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
/************************************************************************
 * © 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.CdsElementNotFoundException;
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) {
		CdsStructuredType targetType = CqnStatementUtils.targetType(cdsModel, query);
		query.accept(new ValidateRefsVisitor(targetType));
		if (query.from().isRef()) {
			CdsEntity entity = CdsModelUtils.entity(cdsModel, query.from().asRef());
			if (!capabilities.supportsViewsWithParameters() && entity.isView() && entity.params().count() > 0) {
				throw new UnsupportedOperationException("Parametrized views are not supported by this data store");
			}
		}
		query.where().ifPresent(w -> w.accept(new VirtualElementValidator(targetType)));
		query.having().ifPresent(h -> h.accept(new VirtualElementValidator(targetType)));
	}

	private static class ValidateRefsVisitor implements CqnVisitor {

		private CdsStructuredType root;

		public ValidateRefsVisitor(CdsStructuredType rowType) {
			root = rowType;
		}

		@Override
		public void visit(CqnElementRef ref) {
			try {
				if (!$KEY.equals(ref.lastSegment()) && !CdsModelUtils.isContextElementRef(ref)) {
					CdsModelUtils.element(root, ref);
				}
			} catch (CdsElementNotFoundException e) {
				throw new CqnValidationException(e.getMessage());
			}
		}
	}

	@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
		}
		CdsStructuredType rowType = CdsModelUtils.entity(cdsModel, update.ref());
		update.where().ifPresent(w -> w.accept(new ValidateRefsVisitor(rowType)));
		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.keyNames(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).isPresent()) {
			usedElements.forEach(element -> entity.findElement(element).orElseThrow(() -> new CqnValidationException(
					"Element '" + element + "' does not exist in entity '" + entity.getQualifiedName() + "'")));
		}
	}

	@Override
	public void validate(CqnDelete delete) {
		String entityName = delete.ref().firstSegment();

		getEntity(entityName);

		CdsStructuredType targetType = CqnStatementUtils.rowType(cdsModel, delete.ref());
		delete.where().ifPresent(w -> w.accept(new VirtualElementValidator(targetType)));

	}

	private CdsEntity getEntity(String entityName) {
		return cdsModel.findEntity(entityName)
				.orElseThrow(() -> new CqnValidationException("Entity '" + entityName + "' does not exist"));
	}

	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.isContextElementRef(ref)) {
				return;
			}
			if (element(root, ref).isVirtual()) {
				throw new IllegalArgumentException(String.format(
						"Virtual element '%s' is not allowed in 'where' or 'having' clause", ref.displayName()));
			}
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy