package com.flipkart.foxtrot.sql.responseprocessors;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.flipkart.foxtrot.common.ActionRequest;
import com.flipkart.foxtrot.common.Document;
import com.flipkart.foxtrot.common.ResponseVisitor;
import com.flipkart.foxtrot.common.count.CountResponse;
import com.flipkart.foxtrot.common.distinct.DistinctResponse;
import com.flipkart.foxtrot.common.group.GroupRequest;
import com.flipkart.foxtrot.common.group.GroupResponse;
import com.flipkart.foxtrot.common.histogram.HistogramResponse;
import com.flipkart.foxtrot.common.query.QueryResponse;
import com.flipkart.foxtrot.common.stats.StatsResponse;
import com.flipkart.foxtrot.common.stats.StatsTrendResponse;
import com.flipkart.foxtrot.common.stats.StatsTrendValue;
import com.flipkart.foxtrot.common.trend.TrendResponse;
import com.flipkart.foxtrot.sql.responseprocessors.model.FieldHeader;
import com.flipkart.foxtrot.sql.responseprocessors.model.FlatRepresentation;
import com.flipkart.foxtrot.sql.responseprocessors.model.MetaData;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.*;
import java.util.stream.Collectors;
import static com.flipkart.foxtrot.sql.responseprocessors.FlatteningUtils.generateFieldMappings;
import static com.flipkart.foxtrot.sql.responseprocessors.FlatteningUtils.genericParse;
public class Flattener implements ResponseVisitor {
private FlatRepresentation flatRepresentation;
private ObjectMapper objectMapper;
private ActionRequest request;
private final List<String> fieldsToReturn;
public Flattener(ObjectMapper objectMapper, ActionRequest request, List<String> fieldsToReturn) {
this.objectMapper = objectMapper;
this.request = request;
this.fieldsToReturn = fieldsToReturn;
}
@Override
public void visit(GroupResponse groupResponse) {
final String separator = "__SEPARATOR__";
Map<String, Integer> fieldNames = Maps.newTreeMap();
Map<String, MetaData> dataFields = generateFieldMappings(null, objectMapper.valueToTree(groupResponse.getResult()), separator);
GroupRequest groupRequest = (GroupRequest) request;
List<Map<String, Object>> rows = Lists.newArrayList();
for (Map.Entry<String, MetaData> groupData : dataFields.entrySet()) {
String values[] = groupData.getKey().split(separator);
Map<String, Object> row = Maps.newHashMap();
for (int i = 0; i < groupRequest.getNesting().size(); i++) {
final String fieldName = groupRequest.getNesting().get(i);
row.put(fieldName, values[i]);
if (!fieldNames.containsKey(fieldName)) {
fieldNames.put(fieldName, 0);
}
fieldNames.put(fieldName, lengthMax(fieldNames.get(fieldName), values[i]));
}
row.put("count", groupData.getValue().getData());
rows.add(row);
}
fieldNames.put("count", 10);
List<FieldHeader> headers = Lists.newArrayList();
for (String fieldName : groupRequest.getNesting()) {
headers.add(new FieldHeader(fieldName, fieldNames.get(fieldName)));
}
headers.add(new FieldHeader("count", 10));
flatRepresentation = new FlatRepresentation("group", headers, rows);
}
@Override
public void visit(HistogramResponse histogramResponse) {
List<Map<String, Object>> rows = Lists.newArrayList();
rows.addAll(histogramResponse.getCounts().stream()
.map(count -> new HashMap<String, Object>() {{
put("timestamp", count.getPeriod());
put("count", count.getCount());
}}).collect(Collectors.toList()));
List<FieldHeader> headers = Lists.newArrayList();
headers.add(new FieldHeader("timestamp", 15));
headers.add(new FieldHeader("count", 15));
flatRepresentation = new FlatRepresentation("histogram", headers, rows);
}
@Override
public void visit(QueryResponse queryResponse) {
Map<String, Integer> fieldNames = Maps.newTreeMap();
List<Map<String, Object>> rows = Lists.newArrayList();
Set<String> fieldToLookup = (null == fieldsToReturn) ? Collections.<String>emptySet() : new HashSet<String>(fieldsToReturn);
boolean isAllFields = fieldToLookup.isEmpty();
for (Document document : queryResponse.getDocuments()) {
Map<String, MetaData> docFields = generateFieldMappings(null, objectMapper.valueToTree(document));
Map<String, Object> row = Maps.newTreeMap();
for (Map.Entry<String, MetaData> docField : docFields.entrySet()) {
String fieldName = docField.getKey();
String prettyFieldName = fieldName.replaceFirst("data.", "");
if (!isAllFields && !fieldToLookup.contains(prettyFieldName)) {
continue;
}
row.put(prettyFieldName, docField.getValue().getData());
if (!fieldNames.containsKey(prettyFieldName)) {
fieldNames.put(prettyFieldName, 0);
}
fieldNames.put(prettyFieldName,
Math.max(fieldNames.get(prettyFieldName), docField.getValue().getLength()));
}
rows.add(row);
}
if (!rows.isEmpty()) {
flatRepresentation = new FlatRepresentation("query", getFieldsFromList(fieldNames), rows);
}
}
@Override
public void visit(StatsResponse statsResponse) {
flatRepresentation = genericParse(objectMapper.valueToTree(statsResponse.getResult()));
List<FieldHeader> headers = Lists.newArrayList();
headers.add(new FieldHeader("percentiles.1.0", 20));
headers.add(new FieldHeader("percentiles.5.0", 20));
headers.add(new FieldHeader("percentiles.25.0", 20));
headers.add(new FieldHeader("percentiles.50.0", 20));
headers.add(new FieldHeader("percentiles.75.0", 20));
headers.add(new FieldHeader("percentiles.95.0", 20));
headers.add(new FieldHeader("percentiles.99.0", 20));
headers.add(new FieldHeader("stats.count", 20));
headers.add(new FieldHeader("stats.avg", 20));
headers.add(new FieldHeader("stats.max", 20));
headers.add(new FieldHeader("stats.min", 20));
headers.add(new FieldHeader("stats.sum", 20));
headers.add(new FieldHeader("stats.sum_of_squares", 20));
headers.add(new FieldHeader("stats.variance", 20));
headers.add(new FieldHeader("stats.std_deviation", 20));
flatRepresentation.setHeaders(headers);
flatRepresentation.setOpcode("stats");
}
@Override
public void visit(StatsTrendResponse statsTrendResponse) {
Set<FieldHeader> headers = Sets.newHashSet();
List<Map<String, Object>> rows = Lists.newArrayList();
for (StatsTrendValue statsTrendValue : statsTrendResponse.getResult()) {
FlatRepresentation tmpFlatR = genericParse(objectMapper.valueToTree(statsTrendValue));
headers.addAll(tmpFlatR.getHeaders());
rows.add(tmpFlatR.getRows().get(0));
}
List<FieldHeader> fieldHeaders = Lists.newArrayList();
fieldHeaders.add(new FieldHeader("period", 20));
fieldHeaders.add(new FieldHeader("percentiles.1.0", 20));
fieldHeaders.add(new FieldHeader("percentiles.5.0", 20));
fieldHeaders.add(new FieldHeader("percentiles.25.0", 20));
fieldHeaders.add(new FieldHeader("percentiles.50.0", 20));
fieldHeaders.add(new FieldHeader("percentiles.75.0", 20));
fieldHeaders.add(new FieldHeader("percentiles.95.0", 20));
fieldHeaders.add(new FieldHeader("percentiles.99.0", 20));
fieldHeaders.add(new FieldHeader("stats.count", 20));
fieldHeaders.add(new FieldHeader("stats.avg", 20));
fieldHeaders.add(new FieldHeader("stats.max", 20));
fieldHeaders.add(new FieldHeader("stats.min", 20));
fieldHeaders.add(new FieldHeader("stats.sum", 20));
fieldHeaders.add(new FieldHeader("stats.sum_of_squares", 20));
fieldHeaders.add(new FieldHeader("stats.variance", 20));
fieldHeaders.add(new FieldHeader("stats.std_deviation", 20));
flatRepresentation = new FlatRepresentation("statstrend", fieldHeaders, rows);
}
@Override
public void visit(TrendResponse trendResponse) {
List<FieldHeader> headers = Lists.newArrayListWithCapacity(3);
JsonNode root = objectMapper.valueToTree(trendResponse.getTrends());
if (null == root || !root.isObject()) {
return;
}
List<String> types = Lists.newArrayList();
List<Map<String, Object>> rows = Lists.newArrayList();
Iterator<String> typeNameIt = root.fieldNames();
Map<String, Map<String, Object>> representation = Maps.newTreeMap();
int typeNameMaxLength = 0;
while (typeNameIt.hasNext()) {
String typeName = typeNameIt.next();
types.add(typeName);
typeNameMaxLength = Math.max(typeNameMaxLength, typeName.length());
for (JsonNode dataNode : root.get(typeName)) {
final String time = Long.toString(dataNode.get("period").asLong());
if (!representation.containsKey(time)) {
representation.put(time, Maps.<String, Object>newHashMap());
}
representation.get(time).put(typeName, dataNode.get("count").asLong());
}
}
headers.add(new FieldHeader("time", 20));
for (String type : types) {
headers.add(new FieldHeader(type, 20));
}
for (Map.Entry<String, Map<String, Object>> element : representation.entrySet()) {
Map<String, Object> row = Maps.newTreeMap();
row.put("time", element.getKey());
for (Map.Entry<String, Object> data : element.getValue().entrySet()) {
row.put(data.getKey(), data.getValue());
}
rows.add(row);
}
flatRepresentation = new FlatRepresentation("trend", new ArrayList<>(headers), rows);
}
@Override
public void visit(CountResponse countResponse) {
FieldHeader fieldHeader = new FieldHeader("count", 20);
List<Map<String, Object>> rows = Lists.newArrayList();
rows.add(Collections.<String, Object>singletonMap("count", countResponse.getCount()));
flatRepresentation = new FlatRepresentation("count", Arrays.asList(fieldHeader), rows);
}
@Override
public void visit(DistinctResponse distinctResponse) {
List<FieldHeader> fieldHeaders = Lists.newArrayList();
for (String header : distinctResponse.getHeaders()) {
fieldHeaders.add(new FieldHeader(header, 10));
}
List<List<String>> distinctResponseRows = distinctResponse.getResult();
List<Map<String, Object>> rows = Lists.newArrayList();
for (List<String> responseRow : distinctResponseRows) {
Map<String, Object> row = Maps.newHashMap();
for (int i = 0; i < fieldHeaders.size(); i++) {
row.put(fieldHeaders.get(i).getName(), responseRow.get(i));
}
rows.add(row);
}
flatRepresentation = new FlatRepresentation("distinct", fieldHeaders, rows);
}
public FlatRepresentation getFlatRepresentation() {
return flatRepresentation;
}
private int lengthMax(int currMax, final String rhs) {
return currMax > rhs.length() ? currMax : rhs.length();
}
private List<FieldHeader> getFieldsFromList(Map<String, Integer> fieldNames) {
List<FieldHeader> headers = Lists.newArrayList();
if (null == fieldsToReturn || fieldsToReturn.isEmpty()) {
for (String fieldName : fieldNames.keySet()) {
headers.add(new FieldHeader(fieldName, fieldNames.get(fieldName)));
}
} else {
for (String fieldName : fieldsToReturn) {
headers.add(new FieldHeader(fieldName, fieldNames.get(fieldName)));
}
}
return headers;
}
}