This commit is contained in:
@@ -886,884 +886,124 @@ namespace AyaNova.DataList
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
//Column name
|
||||
sb.Append(SqlColumnNameToFilter);
|
||||
sb.Append(" ");
|
||||
//handle null values separately
|
||||
if (DataType != UiFieldDataType.Tags && sValue == "*NULL*")
|
||||
{
|
||||
switch (DataType)
|
||||
{
|
||||
//ALL TEXT TYPES
|
||||
case UiFieldDataType.Text:
|
||||
case UiFieldDataType.PhoneNumber:
|
||||
case UiFieldDataType.EmailAddress:
|
||||
case UiFieldDataType.HTTP:
|
||||
{
|
||||
if (sOperator == DataListFilterComparisonOperator.Equality)
|
||||
{
|
||||
// sb.Append(SqlColumnNameToFilter);
|
||||
// sb.Append(" ");
|
||||
|
||||
sb.Append("Is Null");
|
||||
sb.Append(" OR ");
|
||||
sb.Append(SqlColumnNameToFilter);
|
||||
sb.Append(" = ''");
|
||||
}
|
||||
else
|
||||
sb.Append(" <> ''");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (sOperator == DataListFilterComparisonOperator.Equality)
|
||||
sb.Append("Is Null");
|
||||
else
|
||||
sb.Append("Is Not Null");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//non null value
|
||||
//Special addition to handle nulls
|
||||
|
||||
if (DataType != UiFieldDataType.Tags)
|
||||
{
|
||||
switch (sOperator)
|
||||
|
||||
//Build tags filter fragment
|
||||
//https://www.postgresql.org/docs/current/functions-array.html
|
||||
//https://www.postgresql.org/docs/9.1/functions-array.html
|
||||
|
||||
/*
|
||||
For these reasons, am requesting the ability to filter on the Tag column to include:
|
||||
|
||||
**(no value) - to filter to show records that have no tags
|
||||
|
||||
**(has value) - to filter to show records that have any tags
|
||||
|
||||
**Not equal to - to filter to show records that do NOT have a specific tag
|
||||
|
||||
**Contains - to filter to show records that have tags with this text as part of the tag (i.e. be able to type in the text workordercategory so would return ALL workorder records that have one or more tags that have the text workordercategory in it - such as "inspection.workordercategory" or "service.workordercategory" or any one of the unit service types that now are a tag)
|
||||
**NotContains MY ADDITION see below
|
||||
|
||||
//NOTE: all tags are lowercase so case is not an issue really with any of these queries
|
||||
|
||||
array operators for queries and examples:
|
||||
where 'red'=ANY(tags) //case sensitive, matches any single item in array, must be in that order or it will bomb if ANY is first
|
||||
|
||||
//contains operator is the one I've been using: sbTags.Append("@> array[");
|
||||
|
||||
|
||||
Filter ui builder allows multiple tag selection just like any other tag selection and sends the filter to the server like this:
|
||||
filter "[{\"column\":\"customertags\",\"any\":false,\"items\":[{\"op\":\"=\",\"value\":\"completed.reminder,burnaby.dispatchzone,cabling-fibre.user-skill\"}]}]"
|
||||
Comma separated list.
|
||||
Current equalto filter doesn't work with this type of query as it directly compares it as if it was one string instead of does it contain all those terms
|
||||
|
||||
### HOW IT SHOULD WORK ###
|
||||
|
||||
Equality: Exactly equal (aside from order) compare entire search term array to tag array in db - all terms in search exactly present and no others in db record
|
||||
contains and also length is same as search terms length, in this example 1 is the number of search terms as the search is for {'green'}
|
||||
WHERE ARRAY['green'::varchar(255)] <@ tags and array_length(tags,1) = 1
|
||||
|
||||
Not equal: Not EXACTLY Equal (aside from order) All terms in search term array NOT present in All terms in db record. IOW if db record has all search terms but also one more tag then it's NOT equal, Order doesn't matter
|
||||
Specifically exclude rows that exactly match all the search terms
|
||||
WHERE NOT (ARRAY['green'::varchar(255)] <@ tags and array_length(tags,1) = 1)
|
||||
|
||||
NO value: Empty tag array in db record
|
||||
db record tags has NO value
|
||||
where tags = '{}'
|
||||
|
||||
Has value: Non empty tag array in db record
|
||||
db record tags has value
|
||||
where tags <> '{}'
|
||||
|
||||
Contains: All terms in search query present in db record, db record may have other tags but that's ok as long as it has the search term ones (order insensitive)
|
||||
db record Tags contains search tags (and maybe also other tags)
|
||||
WHERE ARRAY['red','green'::varchar(255)] <@ tags
|
||||
|
||||
Not contains: All terms in search query *NOT* present in db record, don't care what else is in db record as long as it's not the search terms (order insensitive)
|
||||
db record Tags DOES NOT contain search tags
|
||||
WHERE NOT ARRAY['red','green'::varchar(255)] <@ tags
|
||||
|
||||
This method and choices ensures users don't need to make a separate row for each one they can just treat the entire tag collection like they treat a single word match in a text field
|
||||
*/
|
||||
switch (sOperator)
|
||||
{
|
||||
case DataListFilterComparisonOperator.Equality:
|
||||
{
|
||||
case DataListFilterComparisonOperator.Equality:
|
||||
//no change on equals for nulls
|
||||
break;
|
||||
case DataListFilterComparisonOperator.GreaterThan:
|
||||
//no change on greater than for nulls
|
||||
//(nulls are going to be assumed to be always at the
|
||||
//less than end of the scale)
|
||||
break;
|
||||
case DataListFilterComparisonOperator.GreaterThanOrEqualTo:
|
||||
//no change on greater than for nulls
|
||||
//(nulls are going to be assumed to be always at the
|
||||
//less than end of the scale)
|
||||
break;
|
||||
case DataListFilterComparisonOperator.LessThan:
|
||||
sb.Append("Is Null OR ");
|
||||
sb.Append(SqlColumnNameToFilter);
|
||||
sb.Append(" ");
|
||||
break;
|
||||
case DataListFilterComparisonOperator.LessThanOrEqualTo:
|
||||
sb.Append("Is Null OR ");
|
||||
sb.Append(SqlColumnNameToFilter);
|
||||
sb.Append(" ");
|
||||
break;
|
||||
case DataListFilterComparisonOperator.NotEqual:
|
||||
//This is the big one:
|
||||
sb.Append("Is Null OR ");
|
||||
sb.Append(SqlColumnNameToFilter);
|
||||
sb.Append(" ");
|
||||
break;
|
||||
StringBuilder sbTags = new StringBuilder();
|
||||
sbTags.Append("@> array[");
|
||||
//Note: with listOptions change to split filter and view the tags are now in sValue as a string of comma delimited strings so split them out here
|
||||
List<string> normalizedTags = TagBiz.NormalizeTags(sValue.Split(',').ToList<string>());
|
||||
foreach (string s in normalizedTags)
|
||||
{
|
||||
|
||||
sbTags.Append($"'{s}',");
|
||||
}
|
||||
sb.Append(sbTags.ToString().TrimEnd(','));
|
||||
sb.Append("::varchar(255)]");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DataListFilterComparisonOperator.NotEqual:
|
||||
sb.Append("<>'");
|
||||
sb.Append(sValue);
|
||||
sb.Append("'");
|
||||
break;
|
||||
|
||||
#region Build for specific type
|
||||
switch (DataType)
|
||||
{
|
||||
//ALL TEXT TYPES
|
||||
case UiFieldDataType.Text:
|
||||
case UiFieldDataType.PhoneNumber:
|
||||
case UiFieldDataType.EmailAddress:
|
||||
case UiFieldDataType.HTTP:
|
||||
//escape any pre-existing apostrophes
|
||||
//i.e. "O'Flaherty's pub"
|
||||
sValue = sValue.Replace("'", "''");
|
||||
case DataListFilterComparisonOperator.NotContains:
|
||||
sb.Append("Not Like '%");
|
||||
sb.Append(sValue);
|
||||
sb.Append("%'");
|
||||
break;
|
||||
|
||||
//case 1951 - unescape ampersands
|
||||
sValue = sValue.Replace("&", "&");
|
||||
case DataListFilterComparisonOperator.Contains:
|
||||
|
||||
//RAVEN NOTE: Decided not to implement TEXT token criteria for now (TTM, probably not useful)
|
||||
//meaning the A-H etc group ranges by alpha bet chunk
|
||||
//TagSpecificWhereFragment = $"(array_to_string({tagColumn.GetSqlValueColumnName()},',') like '%{tagSpecificQuery}%')";
|
||||
sb.Append("Like '%");
|
||||
sb.Append(sValue);
|
||||
sb.Append("%'");
|
||||
break;
|
||||
|
||||
#region Build TEXT OPS criteria
|
||||
switch (sOperator)
|
||||
{
|
||||
case DataListFilterComparisonOperator.Equality:
|
||||
sb.Append("='");
|
||||
sb.Append(sValue);
|
||||
sb.Append("'");
|
||||
break;
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN TAGS");
|
||||
|
||||
case DataListFilterComparisonOperator.GreaterThan:
|
||||
sb.Append(">'");
|
||||
sb.Append(sValue);
|
||||
sb.Append("'");
|
||||
break;
|
||||
}
|
||||
|
||||
case DataListFilterComparisonOperator.GreaterThanOrEqualTo:
|
||||
sb.Append(">='");
|
||||
sb.Append(sValue);
|
||||
sb.Append("'");
|
||||
break;
|
||||
//NOTE: tag filter OPERATOR is ignored, query matches if all tags are found in a tag collection that could have other tags as well
|
||||
|
||||
case DataListFilterComparisonOperator.LessThan:
|
||||
sb.Append("<'");
|
||||
sb.Append(sValue);
|
||||
sb.Append("'");
|
||||
break;
|
||||
//for initial release a tag filter is inclusive of all tags only
|
||||
//in other words all tags presented must be in record to match (simple AND)
|
||||
//select * from awidget where awidget.tags @> array['blah','blah3'::varchar(255)]
|
||||
|
||||
case DataListFilterComparisonOperator.LessThanOrEqualTo:
|
||||
sb.Append("<='");
|
||||
sb.Append(sValue);
|
||||
sb.Append("'");
|
||||
break;
|
||||
//NOTE: After coding this discovered *can* do a LIKE query with tags like this:
|
||||
//(array_to_string(awidget.tags,',') like '%zo%')
|
||||
//implemented that way in picklistquery builder
|
||||
//also ilike is a postgres case insensitive like but works on current locale of server which
|
||||
//sounds like it might fuck up when using other languages so...
|
||||
|
||||
case DataListFilterComparisonOperator.NotEqual:
|
||||
sb.Append("<>'");
|
||||
sb.Append(sValue);
|
||||
sb.Append("'");
|
||||
break;
|
||||
|
||||
|
||||
/* NOTE: CASE - I decided to NOT do case insensitive for now for datalists like I did for picklists because it's a bit of a different situation
|
||||
I would need to make big changes to the select builder and this criteria builder so it's TTM at the moment because it's all tested and working
|
||||
however also the use is a bit different in that it's much more necessary to be hyper accurate here since this drives reporting and potentially a lot
|
||||
of important business data. Matching two different clients inadvertantly because of a case issue would be a bit of a kerfuffle potentially so I'd rather err on the side of accuracy
|
||||
and also I'm not certain how the case code will work with other languages so it's a bit more risky here, if a picklist doesn't work I get a support call but if a report is missing data then maybe
|
||||
the user doesn't realize it and has bad reports.
|
||||
|
||||
code from picklist in case I decide to do it later
|
||||
if (ServerGlobalBizSettings.SearchCaseSensitiveOnly)
|
||||
sWhere = $"({valueColumnName} like '%{autoCompleteQuery}%')";
|
||||
else
|
||||
sWhere = $"(lower({valueColumnName}) like lower('%{autoCompleteQuery}%'))";
|
||||
*/
|
||||
|
||||
//Following 7 operators added 14-June-2006
|
||||
case DataListFilterComparisonOperator.NotContains:
|
||||
sb.Append("Not Like '%");
|
||||
sb.Append(sValue);
|
||||
sb.Append("%'");
|
||||
break;
|
||||
|
||||
case DataListFilterComparisonOperator.Contains:
|
||||
sb.Append("Like '%");
|
||||
sb.Append(sValue);
|
||||
sb.Append("%'");
|
||||
break;
|
||||
|
||||
case DataListFilterComparisonOperator.StartsWith:
|
||||
sb.Append("Like '");
|
||||
sb.Append(sValue);
|
||||
sb.Append("%'");
|
||||
break;
|
||||
|
||||
case DataListFilterComparisonOperator.EndsWith:
|
||||
sb.Append("Like '%");
|
||||
sb.Append(sValue);
|
||||
sb.Append("'");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN STRING");
|
||||
|
||||
}
|
||||
#endregion build text ops criteria
|
||||
break;
|
||||
case UiFieldDataType.Bool:
|
||||
{
|
||||
switch (sOperator)
|
||||
{
|
||||
case DataListFilterComparisonOperator.Equality:
|
||||
sb.Append("= ");
|
||||
if (sValue.ToLowerInvariant() == "true")
|
||||
sb.Append("true");
|
||||
else
|
||||
sb.Append("false");
|
||||
break;
|
||||
|
||||
case DataListFilterComparisonOperator.NotEqual:
|
||||
sb.Append("<> ");
|
||||
if (sValue.ToLowerInvariant() == "true")
|
||||
sb.Append("true");
|
||||
else
|
||||
sb.Append("false");
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] in BOOL");
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
//Note there are three types here for display purposes but all are stored in the db as a timestamp the same with date and time components
|
||||
case UiFieldDataType.Date:
|
||||
case UiFieldDataType.DateTime:
|
||||
case UiFieldDataType.Time:
|
||||
{
|
||||
|
||||
if (sValue.StartsWith("*") && sValue.EndsWith("*"))
|
||||
{
|
||||
//Note: due to date token relativity, all below calcs done with Client provided current date/time and converted to UTC only at the end for sql
|
||||
DateTime ClientToday = clientTimeStamp.Date;
|
||||
DateTime ClientNow = clientTimeStamp.DateTime;
|
||||
|
||||
#region Build criteria for date RANGE TOKEN specified
|
||||
|
||||
//Used as the basis point
|
||||
System.DateTime dtAfter;
|
||||
System.DateTime dtBefore;
|
||||
switch (sValue)
|
||||
{
|
||||
//Case 402
|
||||
case "*yesterday*":
|
||||
//Between Day before yesterday at midnight and yesterday at midnight
|
||||
dtAfter = ClientToday.AddDays(-1);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
dtBefore = ClientToday;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*today*":
|
||||
//Between yesterday at midnight and tommorow at midnight
|
||||
dtAfter = ClientToday.AddSeconds(-1);
|
||||
dtBefore = ClientToday.AddDays(1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
case "*tomorrow*":
|
||||
//Between Tonight at midnight and day after tommorow at midnight
|
||||
dtAfter = ClientToday.AddDays(1).AddSeconds(-1);
|
||||
dtBefore = ClientToday.AddDays(2);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
case "*lastweek*":
|
||||
//Between two Sundays ago at midnight and last sunday at midnight
|
||||
dtAfter = ClientToday;
|
||||
//go back a week
|
||||
dtAfter = dtAfter.AddDays(-7);
|
||||
//go backwards to Sunday
|
||||
while (dtAfter.DayOfWeek != DayOfWeek.Sunday)
|
||||
dtAfter = dtAfter.AddDays(-1);
|
||||
//go to very start of eighth dayahead
|
||||
dtBefore = dtAfter.AddDays(8);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
case "*thisweek*":
|
||||
//Between Sunday at midnight and Next sunday at midnight
|
||||
dtAfter = ClientToday;
|
||||
//go backwards to monday
|
||||
while (dtAfter.DayOfWeek != DayOfWeek.Monday)
|
||||
dtAfter = dtAfter.AddDays(-1);
|
||||
|
||||
//Now go back to sunday last second
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
|
||||
dtBefore = ClientToday;
|
||||
//go forwards to monday
|
||||
if (ClientToday.DayOfWeek == DayOfWeek.Monday)
|
||||
{
|
||||
//Monday today? then go to next monday
|
||||
dtBefore = dtBefore.AddDays(7);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (dtBefore.DayOfWeek != DayOfWeek.Monday)
|
||||
dtBefore = dtBefore.AddDays(1);
|
||||
}
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
case "*nextweek*":
|
||||
//Between Next Sunday at midnight and Next Next sunday at midnight
|
||||
dtAfter = ClientToday;
|
||||
|
||||
//If today is monday skip over it first
|
||||
if (dtAfter.DayOfWeek == DayOfWeek.Monday)
|
||||
dtAfter = dtAfter.AddDays(1);
|
||||
|
||||
//go forwards to next monday
|
||||
while (dtAfter.DayOfWeek != DayOfWeek.Monday)
|
||||
dtAfter = dtAfter.AddDays(1);
|
||||
//Now go back to sunday last second
|
||||
dtAfter = dtAfter.AddDays(-1);
|
||||
//go seven days ahead
|
||||
dtBefore = dtAfter.AddDays(7);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
case "*lastmonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1);
|
||||
//subtract a Month
|
||||
dtAfter = dtAfter.AddMonths(-1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.AddMonths(1);
|
||||
|
||||
//case 1155
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
case "*thismonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.AddMonths(1);
|
||||
|
||||
//case 1155
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*nextmonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1);
|
||||
//Add a Month
|
||||
dtAfter = dtAfter.AddMonths(1);
|
||||
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.AddMonths(1);
|
||||
|
||||
//case 1155
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*14daywindow*":
|
||||
//start with today zero hour
|
||||
dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, ClientToday.Day);
|
||||
dtAfter = dtAfter.AddDays(-7);
|
||||
|
||||
//Add 15 days to get end date (zero hour so not really 15 full days)
|
||||
dtBefore = dtAfter.AddDays(15);
|
||||
|
||||
//case 1155
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*past*":
|
||||
//Forever up to Now
|
||||
dtAfter = new DateTime(1753, 1, 2);//this was for sql server but even then was probably outdated good enough though for our purposes
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*future*":
|
||||
//From Now to forever (999 years from now)
|
||||
dtAfter = ClientNow;
|
||||
dtBefore = dtAfter.AddYears(999);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*lastyear*":
|
||||
//From zero hour january 1 a year ago
|
||||
dtAfter = new DateTime(ClientNow.AddYears(-1).Year, 1, 1, 0, 0, 00).AddSeconds(-1);
|
||||
//To zero hour January 1 this year
|
||||
dtBefore = new DateTime(ClientNow.Year, 1, 1, 0, 0, 00);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*thisyear*":
|
||||
//From zero hour january 1 this year
|
||||
dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1);
|
||||
//To zero hour Jan 1 next year
|
||||
dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*last3months*":
|
||||
//From Now minus 3 months
|
||||
dtAfter = ClientNow.AddMonths(-3).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*last6months*":
|
||||
//From Now minus 6 months
|
||||
dtAfter = ClientNow.AddMonths(-6).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*pastyear*": //within the prior 365 days before today
|
||||
//From Now minus 365 days
|
||||
dtAfter = ClientNow.AddDays(-365).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*past90days*":
|
||||
//From Now minus 90 days
|
||||
dtAfter = ClientNow.AddDays(-90).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
case "*past30days*":
|
||||
//From Now minus 30 days
|
||||
dtAfter = ClientNow.AddDays(-30).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
case "*past7days*":
|
||||
//From Now minus 7 days
|
||||
dtAfter = ClientNow.AddDays(-7).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*past24hours*":
|
||||
//From Now minus 24 hours
|
||||
dtAfter = ClientNow.AddHours(-24).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*past6hours*":
|
||||
//From Now minus 6 hours
|
||||
dtAfter = ClientNow.AddHours(-6).AddSeconds(-1);
|
||||
//To Now
|
||||
dtBefore = ClientNow;
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*january*":
|
||||
//From zero hour january 1 this year
|
||||
dtAfter = new DateTime(ClientNow.Year, 1, 1).AddSeconds(-1);
|
||||
//To zero hour feb 1 this year
|
||||
dtBefore = new DateTime(ClientNow.Year, 2, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*february*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 2, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 3, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*march*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 3, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 4, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*april*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 4, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 5, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*may*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 5, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 6, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*june*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 6, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 7, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*july*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 7, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 8, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*august*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 8, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 9, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*september*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 9, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 10, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*october*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 10, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 11, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*november*":
|
||||
dtAfter = new DateTime(ClientNow.Year, 11, 1).AddSeconds(-1);
|
||||
dtBefore = new DateTime(ClientNow.Year, 12, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*december*":
|
||||
//From zero hour dec 1 this year
|
||||
dtAfter = new DateTime(ClientNow.Year, 12, 1).AddSeconds(-1);
|
||||
//To zero hour Jan 1 next year
|
||||
dtBefore = new DateTime(ClientNow.AddYears(1).Year, 1, 1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
case "*lastyearlastmonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1);
|
||||
//subtract a year and a Month
|
||||
dtAfter = dtAfter.AddYears(-1).AddMonths(-1);
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.AddMonths(1);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*lastyearthismonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1);
|
||||
//subtract a year
|
||||
dtAfter = dtAfter.AddYears(-1);
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.AddMonths(1);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
case "*lastyearnextmonth*":
|
||||
//start with the first day of this month
|
||||
dtAfter = new DateTime(ClientToday.Year, ClientToday.Month, 1);
|
||||
//subtract a year
|
||||
dtAfter = dtAfter.AddYears(-1);
|
||||
//Add a Month
|
||||
dtAfter = dtAfter.AddMonths(1);
|
||||
//Add one month to dtAfter to get end date
|
||||
dtBefore = dtAfter.AddMonths(1);
|
||||
dtAfter = dtAfter.AddSeconds(-1);
|
||||
BuildSQLBetweenTwoTokenDerivedDates(SqlColumnNameToFilter, sb, dtAfter, dtBefore);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException("TOKEN", sOperator, "DataListSqlFilterCriteriaBuilder invalid filter TOKEN type [" + sValue + "] IN DATE_TIME");
|
||||
|
||||
//-----
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
#region Build criteria for date specified
|
||||
//dates come in iso8601 UTC format from the client
|
||||
//suitable for the database to handle as all database dates are in UTC
|
||||
//Local display and parsing will be considered a CLIENT issue at all times
|
||||
|
||||
//comes in UTC, parse converts it to local but touniversal puts it back in utc, weird but only thing that consistently work
|
||||
System.DateTime dtData = DateTime.Parse(sValue).ToUniversalTime();
|
||||
|
||||
//Filter time resolution is in minutes only, you can't select seconds in a filter
|
||||
//so the filter code below must construct a filter time
|
||||
//that falls within the 0th second, 0th millisecond and 59th second and 999th millisecond of the specified time
|
||||
//to ensure it matches the users selections
|
||||
string sDateValueWithZeroSeconds = PostgresDateFormat(ZeroSeconds(dtData));
|
||||
string sDateValueWithMaxSeconds = PostgresDateFormat(MaxSeconds(dtData));
|
||||
|
||||
switch (sOperator)
|
||||
{
|
||||
case DataListFilterComparisonOperator.Equality:
|
||||
sb.Append(">='");
|
||||
sb.Append(sDateValueWithZeroSeconds);
|
||||
sb.Append("' AND ");
|
||||
sb.Append(SqlColumnNameToFilter);
|
||||
sb.Append(" ");
|
||||
sb.Append("<='");
|
||||
sb.Append(sDateValueWithMaxSeconds);
|
||||
sb.Append("'");
|
||||
break;
|
||||
|
||||
case DataListFilterComparisonOperator.GreaterThan:
|
||||
sb.Append(">'");
|
||||
sb.Append(sDateValueWithMaxSeconds);
|
||||
sb.Append("'");
|
||||
break;
|
||||
|
||||
case DataListFilterComparisonOperator.GreaterThanOrEqualTo:
|
||||
sb.Append(">='");
|
||||
sb.Append(sDateValueWithMaxSeconds);
|
||||
sb.Append("'");
|
||||
break;
|
||||
case DataListFilterComparisonOperator.LessThan:
|
||||
sb.Append("<'");
|
||||
sb.Append(sDateValueWithMaxSeconds);
|
||||
sb.Append("'");
|
||||
break;
|
||||
case DataListFilterComparisonOperator.LessThanOrEqualTo:
|
||||
sb.Append("<='");
|
||||
sb.Append(sDateValueWithMaxSeconds);
|
||||
sb.Append("'");
|
||||
break;
|
||||
|
||||
case DataListFilterComparisonOperator.NotEqual:
|
||||
sb.Append("<'");
|
||||
sb.Append(sDateValueWithMaxSeconds);
|
||||
sb.Append("' OR ");
|
||||
sb.Append(SqlColumnNameToFilter);
|
||||
sb.Append(" ");
|
||||
sb.Append(">'");
|
||||
sb.Append(sDateValueWithMaxSeconds);
|
||||
sb.Append("'");
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN DATE_TIME");
|
||||
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UiFieldDataType.Enum://enums are just ints to the db, but it's a special type so the client can recognize it
|
||||
case UiFieldDataType.MemorySize://memory / file size, just a long but this type is for display formatting only
|
||||
case UiFieldDataType.Decimal:
|
||||
case UiFieldDataType.Currency:
|
||||
case UiFieldDataType.InternalId:
|
||||
case UiFieldDataType.Integer: //whole numbers, not only integer
|
||||
{
|
||||
//case 1795 - it's numeric, convert to translation independant format
|
||||
//RAVEN NOTE: no numbers are coming from the client in any culture format other than xx,xxx.00 but this is just insurance for api users
|
||||
NumberFormatInfo nfi = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
|
||||
switch (DataType)
|
||||
{
|
||||
case UiFieldDataType.Decimal:
|
||||
case UiFieldDataType.Currency:
|
||||
{
|
||||
if (nfi.CurrencyDecimalSeparator != ".")
|
||||
{
|
||||
sValue = sValue.Replace(nfi.CurrencyGroupSeparator, "");
|
||||
sValue = sValue.Replace(nfi.CurrencyDecimalSeparator, ".");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UiFieldDataType.Integer:
|
||||
{
|
||||
if (nfi.NumberDecimalSeparator != ".")
|
||||
{
|
||||
sValue = sValue.Replace(nfi.NumberGroupSeparator, "");
|
||||
sValue = sValue.Replace(nfi.NumberDecimalSeparator, ".");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UiFieldDataType.InternalId:
|
||||
{
|
||||
//do nothing, it's a simple number
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sOperator)
|
||||
{
|
||||
case DataListFilterComparisonOperator.Equality:
|
||||
sb.Append("=");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.GreaterThan:
|
||||
sb.Append(">");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.GreaterThanOrEqualTo:
|
||||
sb.Append(">=");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.LessThan:
|
||||
sb.Append("<");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.LessThanOrEqualTo:
|
||||
sb.Append("<=");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.NotEqual:
|
||||
sb.Append("<>");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN NUMBER");
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UiFieldDataType.Tags:
|
||||
{
|
||||
//Build tags filter fragment
|
||||
//https://www.postgresql.org/docs/current/functions-array.html
|
||||
//https://www.postgresql.org/docs/9.1/functions-array.html
|
||||
|
||||
/*
|
||||
For these reasons, am requesting the ability to filter on the Tag column to include:
|
||||
|
||||
**(no value) - to filter to show records that have no tags
|
||||
|
||||
**(has value) - to filter to show records that have any tags
|
||||
|
||||
**Not equal to - to filter to show records that do NOT have a specific tag
|
||||
|
||||
**Contains - to filter to show records that have tags with this text as part of the tag (i.e. be able to type in the text workordercategory so would return ALL workorder records that have one or more tags that have the text workordercategory in it - such as "inspection.workordercategory" or "service.workordercategory" or any one of the unit service types that now are a tag)
|
||||
**NotContains MY ADDITION see below
|
||||
|
||||
//NOTE: all tags are lowercase so case is not an issue really with any of these queries
|
||||
|
||||
array operators for queries and examples:
|
||||
where 'red'=ANY(tags) //case sensitive, matches any single item in array, must be in that order or it will bomb if ANY is first
|
||||
|
||||
//contains operator is the one I've been using: sbTags.Append("@> array[");
|
||||
|
||||
|
||||
Filter ui builder allows multiple tag selection just like any other tag selection and sends the filter to the server like this:
|
||||
filter "[{\"column\":\"customertags\",\"any\":false,\"items\":[{\"op\":\"=\",\"value\":\"completed.reminder,burnaby.dispatchzone,cabling-fibre.user-skill\"}]}]"
|
||||
Comma separated list.
|
||||
Current equalto filter doesn't work with this type of query as it directly compares it as if it was one string instead of does it contain all those terms
|
||||
|
||||
### HOW IT SHOULD WORK ###
|
||||
|
||||
Equality: Exactly equal (aside from order) compare entire search term array to tag array in db - all terms in search exactly present and no others in db record
|
||||
contains and also length is same as search terms length, in this example 1 is the number of search terms as the search is for {'green'}
|
||||
WHERE ARRAY['green'::varchar(255)] <@ tags and array_length(tags,1) = 1
|
||||
|
||||
Not equal: Not EXACTLY Equal (aside from order) All terms in search term array NOT present in All terms in db record. IOW if db record has all search terms but also one more tag then it's NOT equal, Order doesn't matter
|
||||
Specifically exclude rows that exactly match all the search terms
|
||||
WHERE NOT (ARRAY['green'::varchar(255)] <@ tags and array_length(tags,1) = 1)
|
||||
|
||||
NO value: Empty tag array in db record
|
||||
db record tags has NO value
|
||||
where tags = '{}'
|
||||
|
||||
Has value: Non empty tag array in db record
|
||||
db record tags has value
|
||||
where tags <> '{}'
|
||||
|
||||
Contains: All terms in search query present in db record, db record may have other tags but that's ok as long as it has the search term ones (order insensitive)
|
||||
db record Tags contains search tags (and maybe also other tags)
|
||||
WHERE ARRAY['red','green'::varchar(255)] <@ tags
|
||||
|
||||
Not contains: All terms in search query *NOT* present in db record, don't care what else is in db record as long as it's not the search terms (order insensitive)
|
||||
db record Tags DOES NOT contain search tags
|
||||
WHERE NOT ARRAY['red','green'::varchar(255)] <@ tags
|
||||
|
||||
This method and choices ensures users don't need to make a separate row for each one they can just treat the entire tag collection like they treat a single word match in a text field
|
||||
*/
|
||||
switch (sOperator)
|
||||
{
|
||||
case DataListFilterComparisonOperator.Equality:
|
||||
{
|
||||
StringBuilder sbTags = new StringBuilder();
|
||||
sbTags.Append("@> array[");
|
||||
//Note: with listOptions change to split filter and view the tags are now in sValue as a string of comma delimited strings so split them out here
|
||||
List<string> normalizedTags = TagBiz.NormalizeTags(sValue.Split(',').ToList<string>());
|
||||
foreach (string s in normalizedTags)
|
||||
{
|
||||
|
||||
sbTags.Append($"'{s}',");
|
||||
}
|
||||
sb.Append(sbTags.ToString().TrimEnd(','));
|
||||
sb.Append("::varchar(255)]");
|
||||
}
|
||||
break;
|
||||
case DataListFilterComparisonOperator.NotEqual:
|
||||
sb.Append("<>'");
|
||||
sb.Append(sValue);
|
||||
sb.Append("'");
|
||||
break;
|
||||
|
||||
case DataListFilterComparisonOperator.NotContains:
|
||||
sb.Append("Not Like '%");
|
||||
sb.Append(sValue);
|
||||
sb.Append("%'");
|
||||
break;
|
||||
|
||||
case DataListFilterComparisonOperator.Contains:
|
||||
|
||||
//TagSpecificWhereFragment = $"(array_to_string({tagColumn.GetSqlValueColumnName()},',') like '%{tagSpecificQuery}%')";
|
||||
sb.Append("Like '%");
|
||||
sb.Append(sValue);
|
||||
sb.Append("%'");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN TAGS");
|
||||
|
||||
}
|
||||
|
||||
//NOTE: tag filter OPERATOR is ignored, query matches if all tags are found in a tag collection that could have other tags as well
|
||||
|
||||
//for initial release a tag filter is inclusive of all tags only
|
||||
//in other words all tags presented must be in record to match (simple AND)
|
||||
//select * from awidget where awidget.tags @> array['blah','blah3'::varchar(255)]
|
||||
|
||||
//NOTE: After coding this discovered *can* do a LIKE query with tags like this:
|
||||
//(array_to_string(awidget.tags,',') like '%zo%')
|
||||
//implemented that way in picklistquery builder
|
||||
//also ilike is a postgres case insensitive like but works on current locale of server which
|
||||
//sounds like it might fuck up when using other languages so...
|
||||
|
||||
|
||||
}
|
||||
break;
|
||||
case UiFieldDataType.TimeSpan: //TIMESPAN / DURATION
|
||||
{
|
||||
sValue = TimeSpanToPostgresInterval(sValue);
|
||||
/*
|
||||
{ name: vm.$ay.t("GridRowFilterDropDownGreaterThan"), id: ">" },
|
||||
{
|
||||
name: vm.$ay.t("GridRowFilterDropDownGreaterThanOrEqualTo"),
|
||||
id: ">="
|
||||
},
|
||||
{ name: vm.$ay.t("GridRowFilterDropDownLessThan"), id: "<" },
|
||||
{ name: vm.$ay.t("GridRowFilterDropDownLessThanOrEqualTo"), id: "<=" },
|
||||
{ name: vm.$ay.t("GridRowFilterDropDownNotEquals"), id: "!=" }
|
||||
*/
|
||||
switch (sOperator)
|
||||
{
|
||||
case DataListFilterComparisonOperator.Equality:
|
||||
sb.Append("=");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.GreaterThan:
|
||||
sb.Append(">");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.GreaterThanOrEqualTo:
|
||||
sb.Append(">=");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.LessThan:
|
||||
sb.Append("<");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.LessThanOrEqualTo:
|
||||
sb.Append("<=");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
case DataListFilterComparisonOperator.NotEqual:
|
||||
sb.Append("<>");
|
||||
sb.Append(sValue);
|
||||
break;
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException("OPERATOR_TYPE", sOperator, "DataListSqlFilterCriteriaBuilder unhandled operator type [" + sOperator + "] IN TIMESPAN / DURATION");
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new System.ArgumentOutOfRangeException("DATA_TYPE", DataType, "DataListSqlFilterCriteriaBuilder unhandled data type[" + DataType + "]");
|
||||
}
|
||||
#endregion
|
||||
}//end of nonnull path
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user