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

com.sap.cds.impl.qat.Ref2QualifiedColumn Maven / Gradle / Ivy

The newest version!
/************************************************************************
 * © 2019-2023 SAP SE or an SAP affiliate company. All rights reserved. *
 ************************************************************************/
package com.sap.cds.impl.qat;

import static com.sap.cds.util.CdsTypeUtils.hasJsonType;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.base.Strings;
import com.sap.cds.impl.builder.model.ElementRefImpl;
import com.sap.cds.impl.docstore.DocStoreUtils;
import com.sap.cds.impl.localized.LocaleUtils;
import com.sap.cds.impl.sql.SqlMappingImpl;
import com.sap.cds.jdbc.spi.SqlMapping;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnExistsSubquery;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnValidationException;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsStructuredType;

public class Ref2QualifiedColumn implements Function {

	private final QatSelectableNode root;
	private final QatSelectableNode outer;
	private final String rootName;
	private final SqlMapping sqlMapping;
	private final LocaleUtils localeUtils;
	private String collateClause = null;

	public Ref2QualifiedColumn(Function mapping, Deque outer, LocaleUtils localeUtils) {
		Iterator reverse = outer.descendingIterator();
		this.root = qatRoot(reverse.next());
		this.rootName = root.rowType().getQualifiedName();
		this.outer = reverse.hasNext() ? reverse.next() : null;
		this.sqlMapping = mapping.apply(root.rowType());
		this.localeUtils = localeUtils;
	}

	public void startCollate(String collateClause) {
		if (Strings.emptyToNull(collateClause) != null) {
			this.collateClause = " " + collateClause;
		} else {
			stopCollate();
		}
	}

	public void stopCollate() {
		this.collateClause = null;
	}

	@Override
	public String apply(CqnElementRef ref) {
		List segments = ref.segments();
		if (ref.firstSegment().equals(CqnExistsSubquery.OUTER)) {
			return qualifiedColumnName(segments.subList(1, segments.size()), outer);
		}

		return qualifiedColumnName(segments, root);
	}

	private String qualifiedColumnName(List segments, QatNode node) {
		boolean firstSegment = segments.size() > 1;
		String tableAlias;
		String columnName;
		String jsonPath = null;

		for (int i = 0; i < segments.size(); i++) {
			Segment s = segments.get(i);
			if (firstSegment && s.id().equals(rootName)) {
				continue;
			}
			if (node instanceof QatElementNode en && en.isJson()) {
				jsonPath = segments.stream().skip(i).map(Segment::id).collect(Collectors.joining(".", "$.", ""));
				break;
			}

			node = node.child(s.id(), s.filter());
			assertNotNull(segments, node);
			firstSegment = false;
		}

		if (node instanceof QatElementNode) {
			columnName = columnName(node);
		} else {
			throw new CqnValidationException(node.name() + " does not refer to an element");
		}
		do {
			node = node.parent();
		} while (node != null && !(node instanceof QatSelectableNode));
		assertNotNull(segments, node);

		tableAlias = ((QatSelectableNode) node).alias();
		String column = column(tableAlias, columnName);
		if (!Strings.isNullOrEmpty(jsonPath)) {
			column = sqlMapping.jsonQuery(column, jsonPath);
		}
		return column;
	}

	private static String column(String alias, String col) {
		return alias + "." + col;
	}

	private static QatSelectableNode qatRoot(QatNode root) {
		Optional next;
		while ((next = childInSource(root)).isPresent()) {
			root = next.get();
		}

		return (QatSelectableNode) root;
	}

	private static Optional childInSource(QatNode root) {
		return root.children().stream().filter(QatNode::inSource).findFirst();
	}

	private String columnName(QatNode node) {
		QatElementNode elementNode = (QatElementNode) node;
		CdsElement cdsElement = elementNode.element();
		String columnName = sqlMapping.columnName(cdsElement);
		if (node.parent() instanceof QatStructuredElementNode) {
			if (DocStoreUtils.targetsDocStore((CdsStructuredType) cdsElement.getDeclaringType())) {
				return columnName;
			}
			if (cdsElement.findAnnotation(SqlMappingImpl.CDS_PERSISTENCE_NAME).isEmpty()) {
				columnName = structuredColumnName(node);
			}
		}
		// TODO extract COLLATE from column name resolver (issues/1071)
		if (collateClause != null && localeUtils.requiresCollate(cdsElement)) {
			columnName = columnName + collateClause;
		}

		return columnName;
	}

	private String structuredColumnName(QatNode node) {
		List elementName = new ArrayList<>();
		elementName.add(((QatElementNode) node).element().getName());
		QatNode parent = node.parent();
		while (parent instanceof QatStructuredElementNode) {
			QatStructuredElementNode structNode = (QatStructuredElementNode) parent;
			elementName.add(structNode.element().getName());
			parent = structNode.parent();
		}
		Collections.reverse(elementName);
		return sqlMapping.delimitedCasing(String.join("_", elementName));
	}

	private static void assertNotNull(List segments, QatNode node) {
		if (node == null) {
			CqnElementRef ref = ElementRefImpl.elementRef(segments, null, null);
			throw new CqnValidationException("Unresolvable path expression: " + ref.toJson());
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy