com.sap.cds.impl.qat.Ref2QualifiedColumn Maven / Gradle / Ivy
/************************************************************************
* © 2019-2023 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.impl.qat;
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 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 extends Segment> 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 extends Segment> segments, QatNode node) {
boolean firstSegment = segments.size() > 1;
String tableAlias;
String columnName;
for (Segment s : segments) {
if (firstSegment && s.id().equals(rootName)) {
continue;
}
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();
return column(tableAlias, columnName);
}
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 extends Segment> segments, QatNode node) {
if (node == null) {
CqnElementRef ref = ElementRefImpl.elementRef(segments, null, null);
throw new CqnValidationException("Unresolvable path expression: " + ref.toJson());
}
}
}