Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
prerna.reactor.algorithms.NaturalLanguageSearchReactor Maven / Gradle / Ivy
package prerna.reactor.algorithms;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import java.util.stream.Stream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.gson.Gson;
import prerna.algorithm.api.ITableDataFrame;
import prerna.algorithm.api.SemossDataType;
import prerna.auth.utils.SecurityEngineUtils;
import prerna.ds.r.RSyntaxHelper;
import prerna.masterdatabase.utility.MasterDatabaseUtility;
import prerna.query.querystruct.AbstractQueryStruct.QUERY_STRUCT_TYPE;
import prerna.query.querystruct.SelectQueryStruct;
import prerna.query.querystruct.filters.IQueryFilter;
import prerna.query.querystruct.filters.SimpleQueryFilter;
import prerna.query.querystruct.joins.BasicRelationship;
import prerna.query.querystruct.joins.IRelation;
import prerna.query.querystruct.selectors.IQuerySelector;
import prerna.query.querystruct.selectors.IQuerySort;
import prerna.query.querystruct.selectors.QueryColumnOrderBySelector;
import prerna.query.querystruct.selectors.QueryColumnOrderBySelector.ORDER_BY_DIRECTION;
import prerna.query.querystruct.selectors.QueryColumnSelector;
import prerna.query.querystruct.selectors.QueryFunctionSelector;
import prerna.reactor.frame.r.AbstractRFrameReactor;
import prerna.sablecc2.om.GenRowStruct;
import prerna.sablecc2.om.PixelDataType;
import prerna.sablecc2.om.ReactorKeysEnum;
import prerna.sablecc2.om.nounmeta.NounMetadata;
import prerna.util.DIHelper;
import prerna.util.Utility;
public class NaturalLanguageSearchReactor extends AbstractRFrameReactor {
/**
* Generates pixel to dynamically create insight based on Natural Language
* search
*/
private static final Logger classLogger = LogManager.getLogger(NaturalLanguageSearchReactor.class);
protected static final String CLASS_NAME = NaturalLanguageSearchReactor.class.getName();
private static final String DIR_SEPARATOR = java.nio.file.FileSystems.getDefault().getSeparator();
protected static final String GLOBAL = "global";
protected static final String NLDR_DB = "nldr_db";
protected static final String NLDR_JOINS = "nldr_joins";
protected static final String NLDR_MEMBERSHIP = "nldr_membership";
protected static final String IS_GLOBAL = "IS_GLOCAL";
private static LinkedHashMap databaseIdToTypeStore = new LinkedHashMap<>(250);
public NaturalLanguageSearchReactor() {
this.keysToGet = new String[] { ReactorKeysEnum.QUERY_KEY.getKey(), ReactorKeysEnum.DATABASE.getKey(), GLOBAL , ReactorKeysEnum.PANEL.getKey() };
}
@Override
public NounMetadata execute() {
init();
organizeKeys();
String baseFolder = DIHelper.getInstance().getProperty("BaseFolder");
int stepCounter = 1;
Logger logger = this.getLogger(CLASS_NAME);
String query = this.keyValue.get(this.keysToGet[0]);
List dbFilters = getDatabaseIds();
boolean hasFilters = !dbFilters.isEmpty();
boolean global = getGlobal();
String panelId = getPanelId();
// start logger
logger.info(stepCounter + ". Performing Natural Language Search");
stepCounter++;
// Check Packages
String[] packages = new String[] { "data.table", "plyr", "udpipe", "stringdist", "igraph", "SteinerNet" };
this.rJavaTranslator.checkPackages(packages);
// Collect all the apps that we will iterate through
if (hasFilters) {
// need to validate that the user has access to these ids
List databaseIds = SecurityEngineUtils.getFullUserEngineIds(this.insight.getUser());
// make sure our ids are a complete subset of the user ids
// user defined list must always be a subset of all the engine ids
if (!databaseIds.containsAll(dbFilters)) {
throw new IllegalArgumentException("Attempting to filter to app ids that user does not have access to or do not exist");
}
} else {
dbFilters = SecurityEngineUtils.getFullUserEngineIds(this.insight.getUser());
}
// init r tables for use between methods
String rSessionTable = "NaturalLangTable" + this.getSessionId().substring(0, 10);
String rSessionJoinTable = "JoinTable" + this.getSessionId().substring(0, 10);
// source the proper script
StringBuilder sb = new StringBuilder();
String rFolderPath = baseFolder + DIR_SEPARATOR + "R" + DIR_SEPARATOR + "AnalyticsRoutineScripts" + DIR_SEPARATOR;
sb.append(("source(\"" + rFolderPath + "template_assembly.R" + "\");").replace("\\", "/"));
if(global) {
sb.append(("source(\"" + rFolderPath + "template_db.R" + "\");").replace("\\", "/"));
} else {
sb.append(("source(\"" + rFolderPath + "template.R" + "\");").replace("\\", "/"));
}
this.rJavaTranslator.runR(sb.toString());
String queryString = "";
query = buildNamedArray(query);
queryString = getQStringFromArray(query);
logger.info(stepCounter + ". Generating search results");
stepCounter++;
List retData = generateAndRunScript(query, dbFilters, rSessionTable, rSessionJoinTable, global);
// check for error
if(retData == null || retData.size() == 0) {
List> retMap = new Vector<>();
NounMetadata noun = new NounMetadata(retMap, PixelDataType.CUSTOM_DATA_STRUCTURE);
return noun;
}
logger.info(stepCounter + ". Generating pixel return from results");
stepCounter++;
List> returnPixels = generatePixels(retData, query, rSessionTable, global, panelId, queryString);
return new NounMetadata(returnPixels, PixelDataType.CUSTOM_DATA_STRUCTURE);
}
private String getQStringFromArray(String query) {
String retString = "";
String space = "";
String[] namedArray = this.rJavaTranslator.getStringArray(query);
for(String ele : namedArray) {
retString += space + ele;
space = " ";
}
return retString;
}
private String buildNamedArray(String query) {
// check for blank
if(query == null || query.isEmpty()) {
query = "[]";
}
// read string into list
List> optMap = new Vector>();
optMap = new Gson().fromJson(query, optMap.getClass());
StringBuilder arrayRsb = new StringBuilder();
StringBuilder namesRsb = new StringBuilder();
String request = "request_" + Utility.getRandomString(6);
String comma = "";
// start rsb's
arrayRsb.append(request + " <- c(");
namesRsb.append("names(" + request + ") <- c(");
// loop through the map
for (Map component : optMap) {
String comp = component.get("component").toString();
String elemToAdd = "";
String elemName = "";
// handle select and group
String[] selects = { "select", "group", "distribution" };
String[] aggregates = { "average", "count", "max", "min", "sum", "stdev" , "unique count" };
String[] dates = { "dayname ", "monthname ", "week ", "quarter ", "year " };
List selectsList = Arrays.asList(selects);
List aggregatesList = Arrays.asList(aggregates);
List datesList = Arrays.asList(dates);
if (selectsList.contains(comp) || aggregatesList.contains(comp) || datesList.contains(comp)) {
List columns = new Vector();
// if aggregate, add the aggregate row
if (!selectsList.contains(comp)) {
// change aggregate to select
if(!comp.equals("group")) {
elemToAdd += "select ";
elemName = "select";
}
// so first add the aggregate row
// add 'f' prior to any aggregate function
if(comp.equals("unique count")) {
elemToAdd += "funiquecount";
} else {
elemToAdd += ("f" + comp);
}
// change the column to arraylist for below
columns.add(component.get("column").toString());
} else {
// change the column to arraylist for below
elemName = comp;
elemToAdd += comp;
columns = (List) component.get("column");
}
// then, add the component and columns
for(String col : columns) {
// add the 'f' if its a date -- assuming the full thing gets passed as a column?
if(Stream.of(dates).anyMatch(col::startsWith)) {
elemToAdd += " f" + col;
} else {
elemToAdd += " " + col;
}
}
}
// handle the based on
else if(comp.startsWith("based on")) {
elemName = "based on";
String agg = comp.substring(9);
// need to include the aggregate 'f'
if(agg.equals("unique count")) {
elemToAdd += (elemName + " funiquecount");
} else {
elemToAdd += (elemName + " f" + agg);
}
elemToAdd += " " + component.get("column");
}
// handle where and having
else if (comp.equals("where") || comp.startsWith("having")) {
if(comp.startsWith("having")) {
elemName = "having";
if(comp.substring(7).equals("unique count")) {
elemToAdd += "having funiquecount";
} else {
elemToAdd += ("having f" + comp.substring(7));
}
} else {
elemName = "where";
elemToAdd += "where";
}
elemToAdd += " " + component.get("column").toString();
// catch the between
if(component.get("operation").toString().startsWith("between")) {
ArrayList values = (ArrayList) component.get("value");
elemToAdd += " between";
elemToAdd += " " + values.get(0) + " and " + values.get(1);
} else {
String op = component.get("operation").toString();
if(op.equals("before")) {
elemToAdd += " <";
} else if(op.equals("after")) {
elemToAdd += " >";
} else {
elemToAdd += " " + op;
}
elemToAdd += " " + component.get("value").toString();
}
}
// handle sort and rank
else if (comp.equals("sort") || comp.equals("rank")) {
elemName = comp;
elemToAdd += comp;
elemToAdd += " " + component.get("column").toString();
elemToAdd += " " + component.get("operation").toString();
if(!comp.equals("sort")) {
elemToAdd += " " + component.get("value").toString();
}
}
// handle position
else if(comp.equals("position")) {
elemName = comp;
elemToAdd += comp;
elemToAdd += " " + component.get("operation").toString();
elemToAdd += " " + component.get("value").toString();
elemToAdd += " " + component.get("column").toString();
}
// put it into the rsb
arrayRsb.append(comma + "'" + elemToAdd + "'");
namesRsb.append(comma + "'" + elemName + "'");
comma = ",";
}
// wrap up arrays
arrayRsb.append(");");
namesRsb.append(");");
// run arrays in r
this.rJavaTranslator.runR(arrayRsb.toString() + namesRsb.toString());
return request;
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
/*
* Generate the R script
*/
/**
* Generate the 2 data.tables based on the table structure and relationships and
* returns back the results from the algorithm
*
* @param query
* @param allApps
* @param dbFilters
* @return
*/
private List generateAndRunScript(String query, List dbFilters, String rSessionTable, String rSessionJoinTable, boolean global) {
String tempResult = "result" + Utility.getRandomString(8);
StringBuilder rsb = new StringBuilder();
String gcToAdd = "";
// read in the rds files if global
// use the frame columns if not
if(global) {
rsb.append(rSessionTable + " <- " + NLDR_DB + ";");
rsb.append(rSessionJoinTable + " <- " + NLDR_JOINS + ";");
// filter the rds files to the engineFilters
String appFilters = "appFilters" + Utility.getRandomString(8);
gcToAdd += "," + appFilters;
rsb.append(appFilters + " <- c(");
String comma = "";
for (String databaseId : dbFilters) {
rsb.append(comma + " \"" + databaseId + "\" ");
comma = " , ";
}
rsb.append(");");
rsb.append(rSessionTable + " <- " + rSessionTable + "[" + rSessionTable + "$AppID %in% " + appFilters + " ,];");
rsb.append(rSessionJoinTable + " <- " + rSessionJoinTable + "[" + rSessionJoinTable + "$AppID %in% " + appFilters + " ,];");
} else {
// build the dataframe of COLUMN and TYPE
ITableDataFrame frame = this.getFrame();
Map colHeadersAndTypes = frame.getMetaData().getHeaderToTypeMap();
List columnList = new Vector();
List tableList = new Vector();
List typeList = new Vector();
List pkList = new Vector();
for(Map.Entry entry : colHeadersAndTypes.entrySet()) {
String col = entry.getKey();
String type = entry.getValue().toString();
if(col.contains("__")) {
col = col.split("__")[1];
}
columnList.add(col);
if(type.equals("INT") || type.equals("DOUBLE")) {
type = "NUMBER";
}
typeList.add(type);
pkList.add("FALSE");
tableList.add(frame.getName() + "._." + frame.getName());
}
// turn into R table
String rColumns = RSyntaxHelper.createStringRColVec(columnList);
String rTables = RSyntaxHelper.createStringRColVec(tableList);
String rTypes = RSyntaxHelper.createStringRColVec(typeList);
String rPK = RSyntaxHelper.createStringRColVec(pkList);
rsb.append(rSessionTable + " <- data.frame(Table = " + rTables + ", Column = " + rColumns + " , Datatype = " + rTypes
+ ", Key = " + rPK + ", stringsAsFactors = FALSE);");
rsb.append(rSessionJoinTable + " <- data.frame(tbl1 = character(0) , tbl2 = character(0) , joinby1 = character(0)"
+ ", joinby2 = character(0) , AppID = character(0), stringsAsFactors = FALSE);");
}
// lets run the function on the filtered apps
if (global) {
rsb.append(tempResult + " <- exec_componentized_query(" + rSessionTable + "," + rSessionJoinTable + "," + query + "," + NLDR_MEMBERSHIP + ");");
} else {
rsb.append(tempResult + " <- exec_componentized_query(" + rSessionTable + "," + rSessionJoinTable + "," + query + ");");
}
// run it
this.rJavaTranslator.runR(rsb.toString());
// get back the data
String[] headerOrdering = this.rJavaTranslator.getColumns(tempResult);
List list = this.rJavaTranslator.getBulkDataRow(tempResult, headerOrdering);
// garbage cleanup
this.rJavaTranslator.executeEmptyR("rm(" + tempResult + gcToAdd + "); gc();");
return list;
}
/**
* Generate the maps with the query information
*
* @param retData
* @param queryInput
* @param queryString
* @return
*/
private List> generatePixels(List retData, String queryInput, String rSessionTable, boolean global, String panelId, String queryString) {
// we do not know how many rows associate with the same QS
// but we know the algorithm only returns one QS per engine
// and the rows are ordered with regards to how the engine comes back
Map qsList = new LinkedHashMap<>();
// when this value doesn't match the previous, we know to add a new QS
String currDatabaseId = null;
String label = null;
SelectQueryStruct curQs = null;
// need to store the collection of "Combined" qs's and their joins that I am
// holding to make sure I don't duplicate and can also use this to push/pull to
// add additional rows
Map combinedQs = new HashMap<>();
// use the joinCombinedResult to merge in the pixel later
List joinCombinedResult = new Vector<>();
// use the these vectors to handle grouping/having/dropping unneeded cols
List aggregateCols = new Vector<>();
List combinedHavingRows = new Vector<>();
LinkedHashSet colsToDrop = new LinkedHashSet<>();
LinkedHashSet pickedCols = new LinkedHashSet<>();
LinkedHashSet groupedCols = new LinkedHashSet<>();
boolean hasDateGroup = false;
for (int i = 0; i < retData.size(); i++) {
Object[] row = retData.get(i);
// if it is an error
// continue through the loop
String part = row[3].toString();
String rowLabel = row[0].toString();
boolean combined = rowLabel.equals("combined");
if (part.equalsIgnoreCase("error")) {
continue;
}
if (label == null || !label.equals(rowLabel)) {
label = rowLabel;
}
// figure out whether this row is the first of a new qs
String rowDatabaseId = row[1].toString();
if (combined && !combinedQs.containsKey(rowDatabaseId)) {
// this is the combined result where the qs is not created yet
// meaning it is the first of a certain select of a combined entry
currDatabaseId = rowDatabaseId;
curQs = new SelectQueryStruct();
if(global) {
curQs.setQsType(QUERY_STRUCT_TYPE.ENGINE);
curQs.setEngineId(currDatabaseId);
} else {
curQs.setQsType(QUERY_STRUCT_TYPE.FRAME);
}
qsList.put("Multiple" + combinedQs.size(), curQs);
combinedQs.put(currDatabaseId, curQs);
} else if (!combined && currDatabaseId == null) {
// this is the first one of a non-combined
currDatabaseId = rowDatabaseId;
curQs = new SelectQueryStruct();
if(global) {
curQs.setQsType(QUERY_STRUCT_TYPE.ENGINE);
curQs.setEngineId(currDatabaseId);
} else {
curQs.setQsType(QUERY_STRUCT_TYPE.FRAME);
}
qsList.put(label, curQs);
} else if (!combined && currDatabaseId != null && !currDatabaseId.equals(rowDatabaseId)) {
// okay this row is now starting a new QS
// we gotta init another one
currDatabaseId = rowDatabaseId;
curQs = new SelectQueryStruct();
if(global) {
curQs.setQsType(QUERY_STRUCT_TYPE.ENGINE);
curQs.setEngineId(currDatabaseId);
} else {
curQs.setQsType(QUERY_STRUCT_TYPE.FRAME);
}
qsList.put(label, curQs);
} else if (!combined && currDatabaseId != null && currDatabaseId.equals(rowDatabaseId) && !qsList.containsKey(label)) {
// How do we handle multiple queries from the same database
curQs = new SelectQueryStruct();
if (global) {
curQs.setQsType(QUERY_STRUCT_TYPE.ENGINE);
curQs.setEngineId(currDatabaseId);
} else {
curQs.setQsType(QUERY_STRUCT_TYPE.FRAME);
}
qsList.put(label, curQs);
}
// if this is a combined row, pull the qs that matches the appid
if (combined) {
currDatabaseId = rowDatabaseId;
curQs = combinedQs.get(currDatabaseId);
}
if (curQs == null) {
throw new NullPointerException("curQs (Query Struct) should not be null here.");
}
// check what type of row it is, then add to qs by case
if (part.equalsIgnoreCase("select")) {
String selectConcept = row[4].toString();
String selectProperty = row[5].toString();
boolean agg = !row[6].toString().isEmpty();
IQuerySelector selector = null;
// need to properly send/receive null values in case there is a
// property with the same name as the node
boolean isPK = checkForPK(selectConcept, selectProperty, rSessionTable, currDatabaseId, global);
if (isPK) {
selector = new QueryColumnSelector(selectConcept);
} else {
selector = new QueryColumnSelector(selectConcept + "__" + selectProperty);
}
// grab the pick cols and otherwise get columns to drop
// do not need to add agg columns at this point
if (!agg && combined && row[12].toString().equals("yes")) {
pickedCols.add(selector.getAlias());
groupedCols.add(selector.getAlias());
} else if (agg && combined && row[12].toString().equals("yes")) {
// convert avg to average to match selector alias
if (agg && row[6].toString().equals("Avg")) {
pickedCols.add("Average_" + row[5]);
} else {
pickedCols.add(row[6] + "_" + row[5]);
}
} else if (combined && !row[12].toString().equals("yes")) {
colsToDrop.add(selector.getAlias());
}
if (agg) {
// if it is combined, then just import the data for now and save the agg for
// later
if (combined) {
curQs.addSelector(selector);
aggregateCols.add(row);
} else {
QueryFunctionSelector fSelector = new QueryFunctionSelector();
String func = row[6].toString();
fSelector.setFunction(func);
fSelector.addInnerSelector(selector);
// check if it was a date group
String[] dates = { "DAYNAME", "MONTHNAME", "WEEK", "QUARTER", "YEAR" };
if(Stream.of(dates).anyMatch(func.toUpperCase()::startsWith)) {
fSelector.setDataType("String");
hasDateGroup = true;
}
// add the selector
curQs.addSelector(fSelector);
}
} else {
curQs.addSelector(selector);
}
} else if (part.equalsIgnoreCase("from")) {
// if the two appids are filled in but are not equal, this is a join across
// query structures
// therefore, do not add relation but add to a list to be used later
if (!row[1].equals(row[2]) && !row[1].toString().isEmpty() && !row[2].toString().isEmpty()) {
// store this row to help build merge pixel later
joinCombinedResult.add(row);
} else if (!row[5].toString().isEmpty()) {
// this is a join within the same database
String fromConcept = row[4].toString();
String toConcept = row[6].toString();
String joinType = "inner.join";
curQs.addRelation(fromConcept, toConcept, joinType);
}
} else if (part.equalsIgnoreCase("where")) {
String whereTable = row[4].toString();
String whereCol = row[5].toString();
String comparator = row[6].toString();
// if where value 2 is empty
// where value is a scalar
// if where value 2 is not empty
// what means where value is a table name
// and where value 2 is a column name
Object whereValue = row[7];
Object whereValue2 = row[8];
Object whereValueAgg = row[9];
// if it is a table
// we do not know the correct primary key
// so we exec a query to determine if we should use the current selectedProperty
// or keep it as PRIM_KEY_PLACEHOLDER
IQuerySelector selector = null;
// need to properly send/receive null values in case there is a
// property with the same name as the node
boolean isPK = checkForPK(whereTable, whereCol, rSessionTable, currDatabaseId, global);
if (isPK) {
selector = new QueryColumnSelector(whereTable);
} else {
selector = new QueryColumnSelector(whereTable + "__" + whereCol);
}
NounMetadata lhs = new NounMetadata(selector, PixelDataType.COLUMN);
if (!whereValueAgg.toString().isEmpty()) {
// let us address the portion when we have a
// min or max on another column
// so WhereValueAgg is min/max
// WhereValue is Table and WhereValue2 is Column
QueryFunctionSelector fSelector = new QueryFunctionSelector();
fSelector.setFunction(whereValueAgg.toString());
fSelector.addInnerSelector(new QueryColumnSelector(whereValue + "__" + whereValue2));
// add the selector
curQs.addSelector(fSelector);
// add rhs of where
NounMetadata rhs = new NounMetadata(fSelector, PixelDataType.COLUMN);
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
curQs.addExplicitFilter(filter);
} else if (!whereValue2.toString().isEmpty() && !comparator.equals("between")) {
// let us address the portion when we have another column
// so whereValue2 is empty and comparator is not between
// my rhs is another column
NounMetadata rhs = new NounMetadata(new QueryColumnSelector(whereValue + "__" + whereValue2),
PixelDataType.COLUMN);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
curQs.addExplicitFilter(filter);
} else {
// we have to consider the comparators
// so i can do the correct types
if (comparator.contains(">") || comparator.contains("<")) {
// it must numeric
NounMetadata rhs = new NounMetadata(whereValue, PixelDataType.CONST_DECIMAL);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
curQs.addExplicitFilter(filter);
} else if (comparator.equals("between")) {
// still numeric
// but i need 2 filters
// add the lower bound filter
NounMetadata rhs = new NounMetadata(whereValue, PixelDataType.CONST_DECIMAL);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, ">", rhs);
curQs.addExplicitFilter(filter);
// add the upper bound filter
rhs = new NounMetadata(whereValue2, PixelDataType.CONST_DECIMAL);
// add this filter
filter = new SimpleQueryFilter(lhs, "<", rhs);
curQs.addExplicitFilter(filter);
} else {
PixelDataType type = PixelDataType.CONST_STRING;
if (whereValue instanceof Number) {
type = PixelDataType.CONST_DECIMAL;
}
NounMetadata rhs = new NounMetadata(whereValue, type);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
curQs.addExplicitFilter(filter);
}
}
} else if (part.equalsIgnoreCase("having")) {
// if it is a combined having, store this row and handle later
if (combined) {
combinedHavingRows.add(row);
continue;
}
String havingTable = row[4].toString();
String havingCol = row[5].toString();
String havingAgg = row[6].toString();
String comparator = row[7].toString();
// if having value 2 is empty
// having value is a scalar
// if having value 2 is not empty
// that means having value is a table name
// and having value 2 is a column name
// and having value agg is the aggregate function
Object havingValue = row[8];
Object havingValue2 = row[9];
Object havingValueAgg = row[10];
// if it is a table
// we do not know the correct primary key
// so we exec a query to determine if we should use the current selectedProperty
// or keep it as PRIM_KEY_PLACEHOLDER
IQuerySelector selector = null;
boolean isPK = checkForPK(havingTable, havingCol, rSessionTable, currDatabaseId, global);
if (isPK) {
selector = new QueryColumnSelector(havingTable);
} else {
selector = new QueryColumnSelector(havingTable + "__" + havingCol);
}
QueryFunctionSelector fSelector = new QueryFunctionSelector();
fSelector.setFunction(havingAgg);
fSelector.addInnerSelector(selector);
// add the selector
// curQs.addSelector(fSelector);
// add lhs of having
NounMetadata lhs = new NounMetadata(fSelector, PixelDataType.COLUMN);
// add rhs of having
// let us first address the portion when we have another aggregate
if (!havingValueAgg.toString().isEmpty()) {
// THIS DOESN'T WORK VERY WELL... COMPLICATED QUERY THAT REQUIRES A SUBQUERY
if (havingValueAgg.toString().equalsIgnoreCase("max")) {
// add an order + limit
curQs.setLimit(1);
QueryColumnOrderBySelector orderBy = new QueryColumnOrderBySelector(
havingAgg + "(" + havingTable + "__" + havingCol + ")");
orderBy.setSortDir(QueryColumnOrderBySelector.ORDER_BY_DIRECTION.DESC.toString());
curQs.addOrderBy(orderBy);
} else if (havingValueAgg.toString().equalsIgnoreCase("min")) {
// add an order + limit
curQs.setLimit(1);
QueryColumnOrderBySelector orderBy = new QueryColumnOrderBySelector(
havingAgg + "(" + havingTable + "__" + havingCol + ")");
curQs.addOrderBy(orderBy);
}
// my rhs is another column agg
IQuerySelector selectorR = null;
isPK = checkForPK(havingTable, havingCol, rSessionTable, currDatabaseId, global);
if (isPK) {
selector = new QueryColumnSelector(havingTable);
} else {
selector = new QueryColumnSelector(havingTable + "__" + havingValue2);
}
QueryFunctionSelector fSelectorR = new QueryFunctionSelector();
fSelectorR.setFunction(havingValueAgg.toString());
fSelectorR.addInnerSelector(selectorR);
// add this filter
NounMetadata rhs = new NounMetadata(fSelectorR, PixelDataType.COLUMN);
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
curQs.addHavingFilter(filter);
} else {
// we have to consider the comparators
// so i can do the correct types
if (comparator.contains(">") || comparator.contains("<")) {
// it must numeric
NounMetadata rhs = new NounMetadata(havingValue, PixelDataType.CONST_DECIMAL);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
curQs.addHavingFilter(filter);
} else if (comparator.equals("between")) {
// still numeric
// but i need 2 filters
// add the lower bound filter
NounMetadata rhs = new NounMetadata(havingValue, PixelDataType.CONST_DECIMAL);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, ">", rhs);
curQs.addHavingFilter(filter);
// add the upper bound filter
rhs = new NounMetadata(havingValue2, PixelDataType.CONST_DECIMAL);
// add this filter
filter = new SimpleQueryFilter(lhs, "<", rhs);
curQs.addHavingFilter(filter);
} else {
// this must be an equals or not equals...
PixelDataType type = PixelDataType.CONST_STRING;
if (havingValue instanceof Number) {
type = PixelDataType.CONST_DECIMAL;
}
NounMetadata rhs = new NounMetadata(havingValue, type);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
curQs.addHavingFilter(filter);
}
}
} else if (part.equalsIgnoreCase("group")) {
String groupConcept = row[4].toString();
String groupProperty = row[5].toString();
// do not group the havings in this portion
if (combined) {
continue;
} else {
// if it is a table
// we do not know the correct primary key
// so we exec a query to determine if we should use the current selectedProperty
// or keep it as PRIM_KEY_PLACEHOLDER
boolean isPK = checkForPK(groupConcept, groupProperty, rSessionTable, currDatabaseId, global);
if (isPK) {
curQs.addGroupBy(groupConcept, null);
} else if(hasDateGroup){
// check if it was a date group
String[] dates = { "DAYNAME", "MONTHNAME", "WEEK", "QUARTER", "YEAR" };
if(Stream.of(dates).anyMatch(groupProperty.toUpperCase()::startsWith)) {
QueryFunctionSelector fSelector = new QueryFunctionSelector();
String[] dateGroup = groupProperty.split("_");
QueryColumnSelector groupSelector = new QueryColumnSelector(groupConcept + "__" + dateGroup[1]);
fSelector.setFunction(dateGroup[0]);
fSelector.addInnerSelector(groupSelector);
// add the selector
curQs.addGroupBy(fSelector);
}
} else {
curQs.addGroupBy(groupConcept, groupProperty);
}
}
} else if (part.equalsIgnoreCase("rank")) {
String rankTable = row[4].toString();
String rankCol = row[5].toString();
String rankDir = row[6].toString();
String rankAmount = row[7].toString();
// adjust rank direction
if(rankDir.equals("top")) {
rankDir = ORDER_BY_DIRECTION.DESC.toString();
} else if (rankDir.equals("bottom")) {
rankDir = ORDER_BY_DIRECTION.ASC.toString();
}
boolean isPK = checkForPK(rankTable, rankCol, rSessionTable, currDatabaseId, global);
boolean isDerived = checkForDerived(rankTable, rankCol, rSessionTable, currDatabaseId, global);
QueryColumnOrderBySelector orderBy = null;
if (isPK) {
orderBy = new QueryColumnOrderBySelector(rankTable);
} else if(isDerived){
orderBy = new QueryColumnOrderBySelector(rankCol);
} else {
orderBy = new QueryColumnOrderBySelector(rankTable + "__" + rankCol);
}
orderBy.setSortDir(rankDir);
curQs.addOrderBy(orderBy);
// handle both integer types
int lim = Integer.parseInt(rankAmount);
if (lim >= 0) {
curQs.setLimit(lim);
} else {
curQs.setOffSet(Math.abs(lim));
}
} else if (part.equalsIgnoreCase("sort")) {
String sortTable = row[4].toString();
String sortCol = row[5].toString();
String sortDir = row[6].toString();
if(sortDir.equalsIgnoreCase("ascending")) {
sortDir = ORDER_BY_DIRECTION.ASC.toString();
} else if (sortDir.equals("descending")) {
sortDir = ORDER_BY_DIRECTION.DESC.toString();
}
boolean isPK = checkForPK(sortTable, sortCol, rSessionTable, currDatabaseId, global);
boolean isDerived = checkForDerived(sortTable, sortCol, rSessionTable, currDatabaseId, global);
QueryColumnOrderBySelector orderBy = null;
if (isPK) {
orderBy = new QueryColumnOrderBySelector(sortTable);
} else if(isDerived){
orderBy = new QueryColumnOrderBySelector(sortCol);
} else {
orderBy = new QueryColumnOrderBySelector(sortTable + "__" + sortCol);
}
orderBy.setSortDir(sortDir);
curQs.addOrderBy(orderBy);
}
}
// retMap is full of maps with key = label and value = pixel
List> retMap = new Vector<>();
Map map = new HashMap<>();
// track when the entry changes and setup other vars
String curEntry = null;
String frameName = getCleanFrameName(queryString);
String importPixel = "";
String vizTypesPixel = "";
String vizPixel = "";
LinkedHashSet prevDatabaseIds = new LinkedHashSet<>();
int entryCount = 1;
for (Entry entry : qsList.entrySet()) {
// make the framename unique with a counter
frameName = frameName + "_" + retMap.size();
// first lets check if it is combined
if (entry.getKey().contains("Multiple")) {
// if this is the first instance of a combined result, then start a new map
if (curEntry == null || !curEntry.contains("Multiple")) {
// start the new map
map = new HashMap<>();
curEntry = entry.getKey();
importPixel = "";
vizTypesPixel = "";
vizPixel = "";
// process the qs
SelectQueryStruct qs = entry.getValue();
importPixel += frameExistsPixel(frameName);
importPixel += "CreateFrame ( R ) .as ( [ " + frameName + " ] );";
importPixel += buildImportPixelFromQs(qs, qs.getEngineId(), frameName, false, global);
// in the case where there is only one combined qs, lets return
if (entryCount == qsList.entrySet().size()) {
// return map
map.put("database_id", "Multiple Apps");
map.put("database_name", "Multiple Apps");
map.put("frame_name", frameName);
// put all three pixels in the map
// import pixel
importPixel += dropUnwantedCols(colsToDrop, groupedCols);
importPixel += addGroupingsAndHavings(aggregateCols, groupedCols, combinedHavingRows, frameName, global);
importPixel += "));";
map.put("import_pixel", importPixel);
// viz types pixel
vizTypesPixel = (frameName + " | GetNLPVizOptions(database=[\"Multiple\"],columns=" + pickedCols + ");");
map.put("viz_types_pixel", vizTypesPixel);
// viz pixel
vizPixel += getStartPixel(frameName, panelId);
vizPixel += "Panel ( "+panelId+" ) | SetPanelLabel(\"" + queryString + "\");";
vizPixel += "Panel ( "+panelId+" ) | SetPanelView ( \"visualization\" , \"{\"type\":\"echarts\"} \" ) ;";
vizPixel += (frameName + " | PredictViz(database=[\"Multiple\"],columns=" + pickedCols + ",sortPixel=[\""+getSortPixel(qs,null,frameName)+"\"],panel=[" + panelId + "],vizSelection=[]);");
map.put("viz_pixel", "");
// original workflow
map.put("layout", "NLP");
map.put("columns", pickedCols);
retMap.add(map);
}
// store the previous app id for when we join across db's later
prevDatabaseIds.add(qs.getEngineId());
entryCount++;
}
// if this is the last result, lets return the map. we are done
else if (entryCount == qsList.entrySet().size()) {
// process the qs
SelectQueryStruct qs = entry.getValue();
// return map
map.put("database_id", "Multiple Apps");
map.put("database_name", "Multiple Apps");
map.put("frame_name", frameName);
// put all three pixels in the map
importPixel += buildImportPixelFromQs(qs, qs.getEngineId(), frameName, true, global);
importPixel += addMergePixel(qs, prevDatabaseIds, joinCombinedResult, frameName);
importPixel += dropUnwantedCols(colsToDrop, groupedCols);
importPixel += addGroupingsAndHavings(aggregateCols, groupedCols, combinedHavingRows, frameName, global);
importPixel += "));";
map.put("import_pixel", importPixel);
// viz types pixel
vizTypesPixel = (frameName + " | GetNLPVizOptions(database=[\"Multiple\"],columns=" + pickedCols + ");");
map.put("viz_types_pixel", vizTypesPixel);
// viz pixel
vizPixel = getStartPixel(frameName, panelId);
vizPixel += "Panel ( "+panelId+" ) | SetPanelLabel(\"" + queryString + "\");";
vizPixel += "Panel ( "+panelId+" ) | SetPanelView ( \"visualization\" , \"{\"type\":\"echarts\"} \" ) ;";
vizPixel += (frameName + " | PredictViz(database=[\"Multiple\"],columns=" + pickedCols + ",sortPixel=[\""+getSortPixel(qs,null,frameName)+"\"],panel=[" + panelId + "],vizSelection=[]);");
map.put("viz_pixel", vizPixel);
// original workflow
map.put("layout", "NLP");
map.put("columns", pickedCols);
retMap.add(map);
}
// this is a continuation of a previous result
else {
// add to the existing pixel
SelectQueryStruct qs = entry.getValue();
importPixel += buildImportPixelFromQs(qs, qs.getEngineId(), frameName, true, global);
importPixel += addMergePixel(qs, prevDatabaseIds, joinCombinedResult, frameName);
entryCount++;
// store the previous app id for when we join across db's later
prevDatabaseIds.add(qs.getEngineId());
}
} else {
// if the result is not combined, then there is only one qs
// put it in the map and then return
curEntry = entry.getKey();
map = new HashMap<>();
SelectQueryStruct qs = entry.getValue();
String appId = qs.getEngineId();
String appName = MasterDatabaseUtility.getDatabaseAliasForId(appId);
map.put("database_id", appId);
map.put("database_name", appName);
map.put("frame_name", frameName);
// put all three pixels in the map
// import pixel
importPixel = frameExistsPixel(frameName);
importPixel += "CreateFrame ( R ) .as ( [ " + frameName + " ] );";
importPixel += buildImportPixelFromQs(qs, appId, frameName, false, global);
importPixel += "));";
map.put("import_pixel", importPixel);
// viz types pixel
vizTypesPixel = (frameName + " | GetNLPVizOptions(database=[\"" + appId + "\"],columns=" + getSelectorAliases(qs.getSelectors()) + ");");
map.put("viz_types_pixel", vizTypesPixel);
// viz pixel
vizPixel = getStartPixel(frameName, panelId);
vizPixel += "Panel ( "+panelId+" ) | SetPanelLabel(\"" + queryString + "\");";
vizPixel += "Panel ( "+panelId+" ) | SetPanelView ( \"visualization\" , \"{\"type\":\"echarts\"} \" ) ;";
vizPixel += (frameName + " | PredictViz(database=[\"" + appId + "\"],columns="
+ getSelectorAliases(qs.getSelectors()) + ",sortPixel=[\""+getSortPixel(qs,null,frameName)+"\"],panel=[" + panelId + "],vizSelection=[]);");
map.put("viz_pixel", vizPixel);
// original workflow
map.put("layout", "NLP");
map.put("columns", getSelectorAliases(qs.getSelectors()));
retMap.add(map);
importPixel = "";
vizTypesPixel = "";
vizPixel = "";
entryCount++;
}
}
// return the map
return retMap;
}
private String getCleanFrameName(String queryString) {
queryString = queryString.replaceAll("<=", "less than or equal to");
queryString = queryString.replaceAll("- top", "offset_top");
queryString = queryString.replaceAll("- bottom", "offset_bottom");
queryString = queryString.replaceAll(">=", "greater than or equal to");
queryString = queryString.replaceAll("!=", "not equal to");
queryString = queryString.replaceAll("<", "less than");
queryString = queryString.replaceAll(">", "greater than");
queryString = queryString.replaceAll("=", "equals");
queryString = queryString.replaceAll(" ", "_");
queryString = queryString.replaceAll("-", "_");
queryString = queryString.replaceAll("/", "_");
queryString = queryString.replaceAll("\\\\", "_");
return queryString;
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
/**
* Check the existing R table to determine if a column is a primary key in the
* particular table and app
*
* @param concept
* @param property
* @param rSessionTable
* @param appId
* @return true or false
*/
private boolean checkForPK(String concept, String property, String rSessionTable, String appId, boolean global) {
if(!global) {
return false;
}
StringBuilder rsb = new StringBuilder();
String alteredTable = appId + "._." + concept;
rsb.append("unique(" + rSessionTable + "[");
rsb.append(rSessionTable + "$AppID == \"" + appId + "\" & ");
rsb.append(rSessionTable + "$Table == \"" + alteredTable + "\" & ");
rsb.append(rSessionTable + "$Column == \"" + property + "\"");
rsb.append(",]$Key)");
int nrow = this.rJavaTranslator.getInt("length("+rsb.toString()+")");
if(nrow == 0 ){
return false;
}
String key = this.rJavaTranslator.getString(rsb.toString());
return Boolean.parseBoolean(key);
}
/**
* Check the existing R table to determine if a column is a primary key in the
* particular table and app
*
* @param concept
* @param property
* @param rSessionTable
* @param appId
* @return true or false
*/
private boolean checkForDerived(String concept, String property, String rSessionTable, String appId, boolean global) {
if(!global) {
return false;
}
String splitProperty = "";
String[] types = {"Average","Count","Max","Min","UniqueCount","Stdev"};
boolean noMatches = true;
for(String type : types) {
if(property.startsWith(type)) {
noMatches = false;
splitProperty = property.substring(type.length() + 1);
}
}
if(noMatches) {
return false;
}
StringBuilder rsb = new StringBuilder();
String alteredTable = appId + "._." + concept;
rsb.append("nrow(" + rSessionTable + "[");
rsb.append(rSessionTable + "$AppID == \"" + appId + "\" & ");
rsb.append(rSessionTable + "$Table == \"" + alteredTable + "\" & ");
rsb.append(rSessionTable + "$Column == \"" + splitProperty + "\"");
rsb.append(",]);");
int numRows = this.rJavaTranslator.getInt(rsb.toString());
return numRows > 0;
}
/**
* Add the frameexists
*
* @param frameName
* @return
*/
public String frameExistsPixel(String frameName) {
// create the pixel
StringBuilder psb = new StringBuilder();
psb.append("if( ( ");
psb.append("VariableExists ( '" + frameName + "' ) ");
psb.append(") , (\"Frame Exists\") , (");
// return
return psb.toString();
}
/**
* Build the pixel based on the query struct and app id
*
* @param qs
* @param databaseId
* @param frameName
* @param merge
* @return
*/
public String buildImportPixelFromQs(SelectQueryStruct qs, String databaseId, String frameName, boolean merge, boolean global) {
StringBuilder psb = new StringBuilder();
QUERY_STRUCT_TYPE type = qs.getQsType();
// continue with import if false
if (type == QUERY_STRUCT_TYPE.ENGINE) {
// pull from the appId
psb.append("Database ( database = [ \"" + databaseId + "\" ] ) | ");
} else if (type == QUERY_STRUCT_TYPE.FRAME) {
psb.append("Frame ( frame = [" + this.getFrame().getName() + "] ) | ");
}
// pull the correct columns
Map qsToAlias = new HashMap<>();
List selectors = qs.getSelectors();
StringBuilder aliasStringBuilder = new StringBuilder();
aliasStringBuilder.append(".as ( [ ");
psb.append("Select ( ");
String separator = "";
// loop through the selectors and store their name and alias
for (IQuerySelector sel : selectors) {
String selToAdd = sel.toString();
String selAliasToAdd = sel.getAlias();
psb.append(separator);
aliasStringBuilder.append(separator);
separator = " , ";
psb.append(selToAdd);
aliasStringBuilder.append(selAliasToAdd);
// track list in case we need it
qsToAlias.put(sel.getQueryStructName().toUpperCase(), selAliasToAdd);
}
aliasStringBuilder.append(" ] ) | ");
psb.append(" ) ");
psb.append(aliasStringBuilder);
// bring in the group bys
List groupList = qs.getGroupBy();
// loop through the groups and store their name and alias
if (!groupList.isEmpty()) {
psb.append("Group ( ");
separator = "";
for (IQuerySelector group : groupList) {
psb.append(separator);
separator = " , ";
psb.append(group.toString());
}
psb.append(" ) | ");
}
// bring in the filters
List filters = qs.getCombinedFilters().getFilters();
if (!filters.isEmpty()) {
for (IQueryFilter f : filters) {
// start a new filter
psb.append("Filter ( ");
// assume we only have simple
SimpleQueryFilter simpleF = (SimpleQueryFilter) f;
// left hand side is always a column
NounMetadata lhs = simpleF.getLComparison();
psb.append(lhs.getValue() + "");
if (simpleF.getComparator().equals("=")) {
psb.append(" == ");
} else {
psb.append(" ").append(simpleF.getComparator()).append(" ");
}
NounMetadata rhs = simpleF.getRComparison();
PixelDataType rhsType = rhs.getNounType();
if (rhsType == PixelDataType.COLUMN) {
psb.append(rhs.getValue() + "");
} else if (rhsType == PixelDataType.CONST_STRING) {
Object val = rhs.getValue();
if (val instanceof List) {
List vList = (List) val;
int size = vList.size();
psb.append("[");
for (int i = 0; i < size; i++) {
if (i == 0) {
psb.append("\"").append(vList.get(i)).append("\"");
} else {
psb.append(",\"").append(vList.get(i)).append("\"");
}
}
psb.append("]");
} else {
// if it is an RDF database make sure that the wild card is *
String value = rhs.getValue().toString();
if (value.contains("%") && getAppTypeFromId(databaseId).equals("TYPE:RDF")) {
value = value.replace("%", "/.*");
}
psb.append("\"" + value + "\"");
}
} else {
Object val = rhs.getValue();
if (val instanceof List) {
List vList = (List) val;
int size = vList.size();
psb.append("[");
for (int i = 0; i < size; i++) {
if (i == 0) {
psb.append(vList.get(i));
} else {
psb.append(", ").append(vList.get(i));
}
}
psb.append("]");
} else {
psb.append("\"" + rhs.getValue() + "\"");
}
}
// close this filter
psb.append(") | ");
}
}
// bring in the having filters
List havingFilters = qs.getHavingFilters().getFilters();
if (!havingFilters.isEmpty()) {
// start a new filter
for (IQueryFilter f : havingFilters) {
psb.append("Having ( ");
// assume we only have simple
SimpleQueryFilter simpleF = (SimpleQueryFilter) f;
// left hand side is always an aggregate column
NounMetadata lhs = simpleF.getLComparison();
psb.append(lhs.getValue() + "");
if (simpleF.getComparator().equals("=")) {
psb.append(" == ");
} else {
psb.append(" ").append(simpleF.getComparator()).append(" ");
}
// right hand side can be many things
NounMetadata rhs = simpleF.getRComparison();
PixelDataType rhsType = rhs.getNounType();
if (rhsType == PixelDataType.COLUMN) {
psb.append(rhs.getValue() + "");
} else if (rhsType == PixelDataType.CONST_STRING) {
Object val = rhs.getValue();
if (val instanceof List) {
List vList = (List) val;
int size = vList.size();
psb.append("[");
for (int i = 0; i < size; i++) {
if (i == 0) {
psb.append("\"").append(vList.get(i)).append("\"");
} else {
psb.append(",\"").append(vList.get(i)).append("\"");
}
}
psb.append("]");
} else {
psb.append("\"" + rhs.getValue() + "\"");
}
} else {
// it is a number
Object val = rhs.getValue();
if (val instanceof List) {
List vList = (List) val;
int size = vList.size();
psb.append("[");
for (int i = 0; i < size; i++) {
if (i == 0) {
psb.append(vList.get(i));
} else {
psb.append(", ").append(vList.get(i));
}
}
psb.append("]");
} else {
psb.append(rhs.getValue() + "");
}
}
// close this filter
psb.append(") | ");
}
}
// bring in the relations
Set relations = qs.getRelations();
if (!relations.isEmpty()) {
separator = "";
psb.append("Join ( ");
for (IRelation relationship : relations) {
if(relationship.getRelationType() == IRelation.RELATION_TYPE.BASIC) {
BasicRelationship rel = (BasicRelationship) relationship;
String col1 = rel.getFromConcept();
String joinType = rel.getJoinType();
String col2 = rel.getToConcept();
psb.append(separator);
separator = " , ";
psb.append("( " + col1 + ", ");
psb.append(joinType + ", ");
psb.append(col2 + " ) ");
} else {
classLogger.info("Cannot process relationship of type: " + relationship.getRelationType());
}
}
psb.append(") | ");
}
String sortPixel = getSortPixel(qs, qsToAlias, frameName);
psb.append(sortPixel);
if (qs.getLimit() > 0) {
psb.append("Limit(").append(qs.getLimit()).append(") | ");
}
if(qs.getOffset() > 0) {
psb.append("Offset(").append(qs.getOffset()).append(") | ");
}
// final import statement
if (!merge) {
psb.append("Import ( frame = [ " + frameName + " ] ) ;");
}
// if its from the frame, then remove reference to the frame
String retString = psb.toString();
if(!global) {
retString = retString.replaceAll(this.getFrame().getName() + "__", "");
}
// return the pixel
return retString;
}
/**
* get the sort pixel string -- pulled out because used in two different places
* @param qsToAlias
*/
private String getSortPixel(SelectQueryStruct qs, Map qsToAlias, String frameName) {
List orderBys = qs.getOrderBy();
String retString = "";
boolean replaceFrame = false;
if (orderBys == null || orderBys.isEmpty()) {
return retString;
}
// need to recreate this if called from GeneratePixels function
if(qsToAlias == null) {
qsToAlias = new HashMap();
List selectors = qs.getSelectors();
// loop through the selectors and store their name and alias
for (IQuerySelector sel : selectors) {
String selAliasToAdd = sel.getAlias();
// track list in case we need it
qsToAlias.put(sel.getQueryStructName().toUpperCase(), selAliasToAdd);
}
replaceFrame = true;
}
StringBuilder b = new StringBuilder();
StringBuilder b2 = new StringBuilder();
int i = 0;
for (IQuerySort orderBy : orderBys) {
if(orderBy.getQuerySortType() == IQuerySort.QUERY_SORT_TYPE.COLUMN) {
QueryColumnOrderBySelector columnSort = (QueryColumnOrderBySelector) orderBy;
if (i > 0) {
b.append(", ");
b2.append(", ");
}
if (qsToAlias.containsKey(columnSort.getQueryStructName().toUpperCase())) {
b.append(qsToAlias.get(columnSort.getQueryStructName().toUpperCase()));
} else {
b.append(columnSort.getQueryStructName());
}
b2.append(columnSort.getSortDirString());
i++;
}
}
retString = "Sort(columns=[" + b.toString() + "], sort=[" + b2.toString() + "]) | ";
// if this is being passed to the createviz reactor, need to remove frame name
if(replaceFrame) {
retString = retString.replaceAll(frameName + "__", "");
}
return retString;
}
/**
* get the pixel to merge the db's together
*
* @param qs
* @param prevAppId
* -- to make sure its the correct join
* @param joinCombinedResults
* -- the rows to join across
* @param frameName
* -- to perform the join pixel
* @param qs
*
* @return
*/
private String addMergePixel(SelectQueryStruct qs, LinkedHashSet prevAppIds,
List joinCombinedResult, String frameName) {
// Merge ( joins = [ ( System , right.outer.join , EKTROPY_ITEMS_0722__System )
// ] ) ;
String appId = qs.getEngineId();
String mergeCol = "";
String mergeString = "Merge ( joins = [(";
for (Object[] joinRow : joinCombinedResult) {
// figure out which qs needs to merge, whether
// its first, second, what the column is, etc.
if (joinRow[1].equals(appId) && prevAppIds.contains(joinRow[2].toString())) {
mergeCol = joinRow[5].toString();
} else if (prevAppIds.contains(joinRow[1].toString()) && joinRow[2].equals(appId)) {
mergeCol = joinRow[7].toString();
}
}
mergeString += mergeCol + " , inner.join , " + mergeCol + " ) ] , frame = [" + frameName + "] );";
return mergeString;
}
/**
* get the pre-data import pixel
*
* @param frameName
*
* @return
*/
private String getStartPixel(String frameName, String panelId) {
String addPanelText = panelId;
if(panelId.equals("0")) {
addPanelText = "panel = [ 0 ] , sheet = [ \"0\" ]";
}
String startPixel = "AddPanel ( " + addPanelText + " ) ;";
startPixel += "Panel ( " + panelId + " ) | AddPanelConfig ( config = [ { \"type\" : \"golden\" } ] ) ;";
startPixel += "Panel ( " + panelId + " ) | AddPanelEvents ( { \"onSingleClick\" : { \"Unfilter\" : [ { \"panel\" : \"\" , \"query\" : \"( | UnfilterFrame()); \" , "
+ "\"options\" : { } , \"refresh\" : false , \"default\" : true , \"disabledVisuals\" : [ \"Grid\" ] , \"disabled\" : false } ] } , \"onBrush\" : { \"Filter\" : [ { \"panel\" :"
+ " \"\" , \"query\" : \"if((IsEmpty()),( | UnfilterFrame()), ( | SetFrameFilter(==))); \" , "
+ "\"options\" : { } , \"refresh\" : false , \"default\" : true , \"disabled\" : false } ] } } ) ; Panel ( " + panelId + " ) | RetrievePanelEvents ( ) ;";
return startPixel;
}
/**
* Drop the columns that were not "picked"
*
* @param colsToDrop
* -- columns that were not picked by the query
* @param groupedCols
* -- groupedCols to double check that they werent picked elsewhere
* @return
*/
private String dropUnwantedCols(LinkedHashSet colsToDrop, LinkedHashSet groupedCols) {
StringBuilder psb = new StringBuilder();
String colDropString = "";
boolean dropAtLeastOne = false;
// lets build the string
String comma = "";
for (String col : colsToDrop) {
// make sure that it wasn't "picked" somewhere else
if (groupedCols.contains(col)) {
continue;
}
dropAtLeastOne = true;
colDropString += comma + "\"" + col + "\"";
comma = " , ";
}
// Now lets drop the columns that was the aggregate
if (dropAtLeastOne) {
psb.append("DropColumn ( columns = [ ");
psb.append(colDropString);
psb.append(" ] );");
}
return psb.toString();
}
/**
* Get the selectors' aliases as a list
*
* @param aggregateCols
* -- rows that were aggregates in the R return
* @param groupedCols
* -- the columns that we are grouping the aggregates on
* @return
*/
private String addGroupingsAndHavings(List aggregateCols, LinkedHashSet groupedCols,
List combinedHavingRows, String frameName, boolean global) {
// if there were no aggregates, then ignore this
if (aggregateCols == null || aggregateCols.isEmpty()) {
return "";
}
// create the frame qs and other vars
SelectQueryStruct qs = new SelectQueryStruct();
qs.setQsType(QUERY_STRUCT_TYPE.FRAME);
String colDropString = "";
String mergeString = "Merge ( joins = [ ";
// add the selectors for the groupedCols
// also add them into groupby
String comma = "";
for (String col : groupedCols) {
qs.addSelector(new QueryColumnSelector(col));
qs.addGroupBy(col, null);
mergeString += comma + " ( " + col + " , inner.join , " + col + " ) ";
comma = ",";
// qs.addRelation(col, col, "inner.join");
}
mergeString += "] , frame = [ " + frameName + " ] ) ; ";
// add the selectors for the aggregate columns
comma = "";
for (Object[] aggCol : aggregateCols) {
QueryFunctionSelector fSelector = new QueryFunctionSelector();
fSelector.setFunction(aggCol[6].toString());
fSelector.addInnerSelector(new QueryColumnSelector(aggCol[5].toString()));
// add the selector
qs.addSelector(fSelector);
// also build the string to drop this column
colDropString += comma + "\"" + aggCol[5].toString() + "\"";
comma = " , ";
}
for (Object[] row : combinedHavingRows) {
String lCol = row[6] + "_" + row[5];
String comparator = row[7].toString();
// if having value 2 is empty
// having value is a scalar
// if having value 2 is not empty
// that means having value is a table name
// and having value 2 is a column name
// and having value agg is the aggregate function
Object havingValue = row[8];
Object havingValue2 = row[9];
Object havingValueAgg = row[10];
// if it is a table
// we do not know the correct primary key
// so we exec a query to determine if we should use the current selectedProperty
// or keep it as PRIM_KEY_PLACEHOLDER
IQuerySelector selector = new QueryColumnSelector(lCol);
// add lhs of having
NounMetadata lhs = new NounMetadata(selector, PixelDataType.COLUMN);
// add rhs of having
// let us first address the portion when we have another aggregate
if (!havingValueAgg.toString().isEmpty()) {
// my rhs is another column agg
IQuerySelector selectorR = new QueryColumnSelector(havingValue2.toString());
QueryFunctionSelector fSelectorR = new QueryFunctionSelector();
fSelectorR.setFunction(havingValueAgg.toString());
fSelectorR.addInnerSelector(selectorR);
// add this filter
NounMetadata rhs = new NounMetadata(fSelectorR, PixelDataType.COLUMN);
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
qs.addHavingFilter(filter);
} else {
// we have to consider the comparators
// so i can do the correct types
if (comparator.contains(">") || comparator.contains("<")) {
// it must numeric
NounMetadata rhs = new NounMetadata(havingValue, PixelDataType.CONST_DECIMAL);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
qs.addHavingFilter(filter);
} else if (comparator.equals("between")) {
// still numeric
// but i need 2 filters
// add the lower bound filter
NounMetadata rhs = new NounMetadata(havingValue, PixelDataType.CONST_DECIMAL);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, ">", rhs);
qs.addHavingFilter(filter);
// add the upper bound filter
rhs = new NounMetadata(havingValue2, PixelDataType.CONST_DECIMAL);
// add this filter
filter = new SimpleQueryFilter(lhs, "<", rhs);
qs.addHavingFilter(filter);
} else {
// this must be an equals or not equals...
PixelDataType type = PixelDataType.CONST_STRING;
if (havingValue instanceof Number) {
type = PixelDataType.CONST_DECIMAL;
}
NounMetadata rhs = new NounMetadata(havingValue, type);
// add this filter
SimpleQueryFilter filter = new SimpleQueryFilter(lhs, comparator, rhs);
qs.addHavingFilter(filter);
}
}
}
// create the string and run it
StringBuilder psb = new StringBuilder();
psb.append(buildImportPixelFromQs(qs, null, frameName, true, global));
psb.append(mergeString);
// Now lets drop the columns that was the aggregate
psb.append("DropColumn ( columns = [ ");
psb.append(colDropString);
psb.append(" ] );");
// return
return psb.toString();
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
/*
* Get input from noun store
*/
/**
* Get input database ids
*
* @return
*/
private List getDatabaseIds() {
List engineFilters = new Vector<>();
GenRowStruct engineGrs = this.store.getNoun(this.keysToGet[1]);
for (int i = 0; i < engineGrs.size(); i++) {
engineFilters.add(engineGrs.get(i).toString());
}
return engineFilters;
}
/**
* Get the selectors' aliases as a list
*
* @param qs
* selectors
* @return
*/
private List getSelectorAliases(List selectors) {
List aliases = new Vector<>();
for (IQuerySelector sel : selectors) {
aliases.add(sel.getAlias());
}
return aliases;
}
private boolean getGlobal() {
GenRowStruct grs = this.store.getNoun(this.keysToGet[2]);
if (grs == null || grs.isEmpty()) {
return true;
}
return Boolean.parseBoolean(grs.get(0).toString());
}
private String getPanelId() {
// see if defined as individual key
GenRowStruct columnGrs = this.store.getNoun(ReactorKeysEnum.PANEL.getKey());
if (columnGrs != null) {
if (columnGrs.size() > 0) {
return columnGrs.get(0).toString();
}
}
return "0";
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
/*
* Utility
*/
/**
* Utilize a static cache so we do not query local master to get the type of an
* database every time
*
* @param databaseId
* @return
*/
private static String getAppTypeFromId(String databaseId) {
String type = databaseIdToTypeStore.get(databaseId);
if (type != null) {
return type;
}
// store the result so we don't need to query all the time
type = MasterDatabaseUtility.getDatabaseTypeForId(databaseId);
databaseIdToTypeStore.put(databaseId, type);
if (databaseIdToTypeStore.size() > 200) {
synchronized (databaseIdToTypeStore) {
if (databaseIdToTypeStore.size() > 100) {
// it should be ordered from first to last
Iterator it = databaseIdToTypeStore.keySet().iterator();
int counter = 0;
while (it.hasNext() && counter < 100) {
databaseIdToTypeStore.remove(it.next());
}
}
}
}
return type;
}
}