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

com.sap.cds.impl.PreparedCqnStmt 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 java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;

import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.io.Reader;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

import com.sap.cds.CdsDataStoreException;
import com.sap.cds.CdsException;
import com.sap.cds.impl.parser.token.Jsonizer;
import com.sap.cds.ql.CdsDataException;
import com.sap.cds.ql.cqn.CqnSelectListItem;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.impl.ExpandProcessor;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.util.CdsTypeUtils;

public class PreparedCqnStmt implements PreparedCqnStatement {

	private final CqnStructuredTypeRef ref;
	private final CdsStructuredType targetType;
	private final String nativeStatement;
	private final List selectListItems;
	private final List expands;
	private final List excluding;
	private final List params;

	private PreparedCqnStmt(CqnStructuredTypeRef ref, CdsStructuredType targetType, String nativeStatement,
			List selectListItems, List expands, List excluding, List parameters) {
		this.ref = ref;
		this.targetType = targetType;
		this.nativeStatement = nativeStatement;
		this.params = parameters;
		this.selectListItems = selectListItems;
		this.expands = expands;
		this.excluding = excluding;
	}

	public static PreparedCqnStmt create(String nativeStatement, List selectListItems,
			List expands, List excluding, List parameters, CqnStructuredTypeRef ref,
			CdsStructuredType targetType) {
		return new PreparedCqnStmt(ref, targetType, nativeStatement, selectListItems, expands, excluding, parameters);
	}

	public static PreparedCqnStmt createUpdate(String nativeStatement, List parameters,
			CqnStructuredTypeRef ref, CdsEntity entity) {
		return new PreparedCqnStmt(ref, entity, nativeStatement, emptyList(), emptyList(), emptyList(), parameters);
	}

	protected String toNative() {
		return nativeStatement;
	}

	public List selectListItems() {
		return unmodifiableList(selectListItems);
	}

	public List expands() {
		return expands;
	}

	public List excluding() {
		return unmodifiableList(excluding);
	}

	@SuppressWarnings("unchecked")
	public  T targetType() {
		return (T) targetType;
	}

	public CqnStructuredTypeRef ref() {
		return ref;
	}

	@Override
	public String toString() {
		return nativeStatement;
	}

	public abstract static class Parameter {
		protected CdsBaseType type;

		public abstract Object get(Map cqnParameters);

		public abstract String name();

		public Optional defaultValue() {
			return Optional.empty();
		}

		public CdsBaseType type() {
			return type;
		}

		@Override
		public String toString() {
			return name();
		}

		public Parameter type(Optional type) {
			this.type = type.map(CdsBaseType::cdsType).orElse(null);
			return this;
		}

		public Parameter type(CdsBaseType type) {
			this.type = type;
			return this;
		}

	}

	public static class CqnParam extends Parameter {
		final String name;
		final Object defaultValue;

		public CqnParam(String name) {
			this(name, null);
		}

		public CqnParam(String name, Object defaultValue) {
			this.name = name;
			this.defaultValue = defaultValue;
		}

		@Override
		public Object get(Map values) {
			if (values.containsKey(name)) {
				Object value = values.get(name);
				if (type == CdsBaseType.UUID) {
					value = CdsTypeUtils.parseUuid(value);
				}
				return value;
			}
			if (defaultValue != null) {
				return defaultValue;
			}
			throw new CdsException("Missing value for parameter " + name);
		}

		@Override
		public String name() {
			return name;
		}

		@Override
		public Optional defaultValue() {
			return Optional.ofNullable(defaultValue);
		}
	}

	public static class DataParam extends Parameter {
		private final String name;
		private final String[] segs;

		public DataParam(String name, CdsBaseType type) {
			this.name = name;
			this.type = type;
			this.segs = name.split("\\.");
		}

		@Override
		public Object get(Map values) {
			if (values == null) {
				return null;
			} 
			Object result = values;
 			for (String seg : segs) {
 				result = asMap(result).get(seg);
 				if (result == null) {
 					return null;
 				}
 			}
			return result;
		}

		@Override
		public String name() {
			return name;
		}
	}

	public static class ValueParam extends Parameter {
		final Supplier valueSupplier;

		public ValueParam(Supplier valueSupplier) {
			this.valueSupplier = valueSupplier;
		}

		@Override
		public Object get(Map cqnParameters) {
			return valueSupplier.get();
		}

		@Override
		public String name() {
			return valueSupplier != null ? valueSupplier.get().toString() : "null";
		}

	}

	public static class JsonParam extends Parameter {

		@Override
		public Reader get(Map map) {

			try {
				PipedWriter writer = new PipedWriter();
				PipedReader reader = new PipedReader(writer);

				//TODO: we need to find a better solution here. Or at least clarify
				//how CompletableFuture.runAsync() behaves in a Spring Boot context. Also,
				//it would be desirable to only start the writer if a reader starts to read.
				CompletableFuture.runAsync(() -> {
					try {
						Jsonizer.write(writer, map);
					} catch (IOException e) {
						throw new CdsDataException("Failed to serialize JSON data parameter: ", e);
					}
				});

				return reader;
			} catch (IOException e) {
				throw new CdsDataStoreException("IO Exception during serialization of JSON content", e);
			}
		}

		@Override
		public CdsBaseType type() {
			return CdsBaseType.LARGE_STRING;
		}

		@Override
		public String name() {
			return "$json";
		}
	}

	public List parameters() {
		return this.params;
	}

	@SuppressWarnings("unchecked")
	private static Map asMap(Object result) {
		try {
			return (Map) result;
		} catch (ClassCastException e) {
			throw new CdsDataException("Data has unexpected type " + result.getClass().getName(), e);
		}
	}

}