Files
raven/server/AyaNova/PickList/PickListSqlBuilder.cs
2020-03-16 19:23:23 +00:00

218 lines
8.8 KiB
C#

using System.Collections.Generic;
using System.Text;
using System.Linq;
using AyaNova.Biz;
namespace AyaNova.PickList
{
internal static class PickListSqlBuilder
{
//Maximum number of results to return at any given time
//did a little research and may adjust this but it can be fairly girthy in this day and age
//and many people might not want or need to autocomplete type if we provide enough leeway.
//for example, if you're selecting a
const int MAXIMUM_RESULT_COUNT = 100;
//Build the query for a picklist request
internal static string Build(IAyaPickList pickList, List<string> templateColumnNames, string autoCompleteQuery, bool IncludeInactive)
{
//determine this in advance as it will be used in a loop later
bool HasAutoCompleteQuery = !string.IsNullOrWhiteSpace(autoCompleteQuery);
//lists to collect the clauses so to avoid comma fuckery
List<string> lSelect = new List<string>();
string ActiveWhereFragment = string.Empty;
List<string> lWhere = new List<string>();
List<string> lOrderBy = new List<string>();
string PlIdSelectFragment = string.Empty;
string ActiveSelectFragment = string.Empty;
//PROCESS ROW ID "VALUE" COLUMN
//
AyaPickListFieldDefinition rowIdColumn = pickList.ColumnDefinitions.FirstOrDefault(x => x.IsRowId == true);
//this should only happen with a development error
if (rowIdColumn == null)
throw new System.ArgumentNullException($"DEV ERROR in PickListSqlBuilder.cs: picklist for {pickList.DefaultListObjectType.ToString()} has no rowId column specified in columnDefinitions list");
PlIdSelectFragment = rowIdColumn.SqlIdColumnName + " as plId";
//PROCESS ACTIVE COLUMN
//
//NOTE: default is to filter *out* inactive objects because that's the most common need at the Client
//but we provide the override for that if necessary as there are often (management usually) cases where user needs to select inactive records
//add active column, fake it if necessary
AyaPickListFieldDefinition activeColumn = pickList.ColumnDefinitions.FirstOrDefault(x => x.IsActiveColumn == true);
if (activeColumn == null)
{
//no active column which is normal for some types of objects
//so make a fake one and return them all as active=true as all lists must return the same format
ActiveSelectFragment = "true as plActive";
}
else
{
//we have an active column, set accordingly
//regardless of wanting to see inactive, we always want to see the column itself
ActiveSelectFragment = activeColumn.SqlValueColumnName + " as plActive";
//this is the normal path unless there is an override
//if there is an override to see inactive too then we just don't set the filter on active
if (!IncludeInactive)
{
ActiveWhereFragment = activeColumn.SqlValueColumnName + " = true";
}
}
//PROCESS TEMPLATED COLUMNS TO BE RETURNED IN RESULTS
//
foreach (string ColumnName in templateColumnNames)
{
AyaPickListFieldDefinition o = pickList.ColumnDefinitions.FirstOrDefault(x => x.FieldKey == ColumnName);
#if (DEBUG)
if (o == null)
{
throw new System.ArgumentNullException($"DEV ERROR in PickListSqlBuilder.cs: field {ColumnName} specified in template was NOT found in columnDefinitions list");
}
#endif
if (o != null)
{//Ignore missing fields in production
var valueColumnName = o.GetSqlValueColumnName();
string sWhere = string.Empty;
//TAGS COLUMN
//
if (o.ColumnDataType == UiFieldDataType.Tags)
{
//Handle tags for each part of the sql query
//idea is they display as a comma separated list,
//filter as a like comparison to each individual tag and
//order by the array which appears in testing to do it left to right as if it was a giant string
lSelect.Add($"(array_to_string({valueColumnName},',')");
//tags can order by without the arraytostring
lOrderBy.Add(valueColumnName);
//THIS is the best filter method for a like comparison to each individual tag:
//(array_to_string(awidget.tags,',') like '%zo%')
if (HasAutoCompleteQuery)
sWhere = $"(array_to_string({valueColumnName},',') like '%{autoCompleteQuery}%')";
}
else if (o.ColumnDataType == UiFieldDataType.Text || o.ColumnDataType == UiFieldDataType.EmailAddress || o.ColumnDataType == UiFieldDataType.HTTP)
{
//TEXT COLUMN
//
lSelect.Add(valueColumnName);
lOrderBy.Add(valueColumnName);
if (HasAutoCompleteQuery)
sWhere = $"({valueColumnName} like '%{autoCompleteQuery}%')";
}
else
{
//NON-TEXT COLUMN
//
//Note: if any part of a select contatenation query using the || postgres concat is text then all fields are automatically converted to text
//so no need to make it text here as the automatic spacing character will force the whole thing to a text concat anyway
//ref: https://stackoverflow.com/a/19943343/8939
lSelect.Add(valueColumnName);
//order by for now seems to be best as just order by it's value whatever it is
lOrderBy.Add(valueColumnName);
//Where fragment is different for non text fields: it needs to be cast to text to like query on it
//(cast (awidget.serial as text) like '%some%')
if (HasAutoCompleteQuery)
sWhere = $"(cast ({valueColumnName} as text) like '%{autoCompleteQuery}%')";
}
if (HasAutoCompleteQuery)
lWhere.Add(sWhere);
}
}
StringBuilder sb = new StringBuilder();
//SELECT
sb.Append("select ");
//ID COLUMN
sb.Append(PlIdSelectFragment);
sb.Append(", ");
//ACTIVE COLUMN
sb.Append(ActiveSelectFragment);
sb.Append(", ");
//select name || ' ' || serial || ' ' || array_to_string(tags,',') as display from awidget
foreach (string s in lSelect)
{
sb.Append(s);
sb.Append(" || ' ' || ");//sb 11 characters
}
//clear trailing concat element
sb.Length -= 11;//length of last concat fragment above
sb.Append(" as plname");
//FROM
sb.Append(" ");
sb.Append(pickList.SQLFrom);
//WHERE
sb.Append(" where ");
if (!IncludeInactive)
{
sb.Append(ActiveWhereFragment);
sb.Append(" and (");
}
foreach (string s in lWhere)
{
sb.Append(s);
sb.Append(" or ");
}
//clear trailing or
sb.Length -= 4;
//enclosing parenthesis
if (!IncludeInactive)
{
sb.Append(")");
}
//ORDER BY
sb.Append(" order by ");
foreach (string s in lOrderBy)
{
sb.Append(s);
sb.Append(",");
}
//clear trailing comma
sb.Length--;
//LIMIT
sb.Append($" limit {MAXIMUM_RESULT_COUNT}");
return sb.ToString();
}
//"select awidget.id as plId || ' 'awidget.active as plActive || ' 'awidget.name || ' 'awidget.serial || ' 'auser.name as plname from awidget left outer join auser on (awidget.userid=auser.id)
//where awidget.active = true and ((awidget.name like '%on%') or (cast (awidget.serial as text) like '%on%') or (auser.name like '%on%')) order by awidget.name,awidget.serial,auser.name limit 100"
}//eoc
}//ens