778 lines
28 KiB
C#
778 lines
28 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Sarand.Net.Standard.Base.App;
|
|
using Sarand.Net.Standard.Base.Core;
|
|
using Sarand.Net.Standard.Base.Utils;
|
|
|
|
namespace Sarand.Net.Standard.Base.Dbas
|
|
{
|
|
//=======================================================================
|
|
// Definitions.
|
|
public enum DbJoinType
|
|
{
|
|
Inner,
|
|
LeftOuter,
|
|
RightOuter,
|
|
FullOuter,
|
|
OuterApply,
|
|
Cross,
|
|
}
|
|
public enum DbOrderByNullMode
|
|
{
|
|
Default,
|
|
DemoteNulls,
|
|
PromoteNulls,
|
|
DateDemoteNulls,
|
|
DatePromoteNulls
|
|
}
|
|
public enum DbDateDiffInterval
|
|
{
|
|
Year,
|
|
Quarter,
|
|
Month,
|
|
DayOfYear,
|
|
Day,
|
|
Weekday,
|
|
Week,
|
|
Hour,
|
|
Minute,
|
|
Second,
|
|
}
|
|
|
|
//=======================================================================
|
|
// Global DBMS.
|
|
// When dealing with databases using different DBMSs, the methods below that
|
|
// depend on the DBMS to correctly format query clauses need to ensure that
|
|
// they are referencing the correct DBMS type.
|
|
// This global value is designed to be populated whenever a Query is created
|
|
// with a specific DBMS specified so that it can be made available to other
|
|
// methods that depend on the value (especially static ones).
|
|
public class DbGlobalDbms
|
|
{
|
|
private DbGlobalDbms() { }
|
|
private static DbmsType dbms = DbmsType.General;
|
|
public static DbmsType Type
|
|
{
|
|
get { return dbms; }
|
|
set { if (value != DbmsType.General) dbms = value; }
|
|
}
|
|
}
|
|
|
|
//=======================================================================
|
|
public class DbQueryJoin
|
|
{
|
|
//-------------------------------------------------------------------
|
|
// Data members.
|
|
|
|
//-------------------------------------------------------------------
|
|
// Construction.
|
|
public DbQueryJoin(DbJoinType type = DbJoinType.Inner)
|
|
{
|
|
this.Type = type;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Properties.
|
|
public DbJoinType Type { get; private set; } = DbJoinType.Inner;
|
|
public String JoinType { get { return GetJoinType(Type); } }
|
|
public StringPair Table { get; set; } = null; /* Second value is the optional alias. */
|
|
public List<String> Filters { get; } = new List<String>();
|
|
public List<DbQueryJoin> SubJoins { get; } = new List<DbQueryJoin>();
|
|
|
|
//-------------------------------------------------------------------
|
|
public static String GetJoinType(DbJoinType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case DbJoinType.LeftOuter: return (DbGlobalDbms.Type == DbmsType.Sage ? "LEFT JOIN" : "LEFT OUTER JOIN");
|
|
case DbJoinType.RightOuter: return (DbGlobalDbms.Type == DbmsType.Sage ? "RIGHT JOIN" : "RIGHT OUTER JOIN");
|
|
case DbJoinType.FullOuter: return (DbGlobalDbms.Type == DbmsType.Sage ? "FULL JOIN" : "FULL OUTER JOIN");
|
|
case DbJoinType.OuterApply: return ("OUTER APPLY");
|
|
case DbJoinType.Cross: return "CROSS JOIN";
|
|
case DbJoinType.Inner:
|
|
default: return "INNER JOIN";
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
public String BuildJoin(String enclosedJoin)
|
|
{
|
|
//---------------------------------------------------------------
|
|
// Select syntax.
|
|
// SELECT [ DISTINCT | ALL ]
|
|
// column_expression1, column_expression2, ....
|
|
// [ FROM from_clause ]
|
|
// [ WHERE where_expression ]
|
|
// [ GROUP BY expression1, expression2, .... ]
|
|
// [ HAVING having_expression ]
|
|
// [ ORDER BY order_column_expr1, order_column_expr2, .... ]
|
|
// column_expression ::= expression [ AS ] [ column_alias ]
|
|
// from_clause ::= select_table1, select_table2, ...
|
|
// from_clause ::= select_table1 LEFT [OUTER] JOIN select_table2 ON expr ...
|
|
// from_clause ::= select_table1 RIGHT [OUTER] JOIN select_table2 ON expr ...
|
|
// from_clause ::= select_table1 [INNER] JOIN select_table2 ...
|
|
// select_table ::= table_name [ AS ] [ table_alias ]
|
|
// select_table ::= ( sub_select_statement ) [ AS ] [ table_alias ]
|
|
// order_column_expr ::= expression [ ASC | DESC ]
|
|
// Issues.
|
|
// MS Access - complex join expressions must be bracketed: ON (expr ...)
|
|
//---------------------------------------------------------------
|
|
String joinData = String.Empty;
|
|
//---------------------------------------------------------------
|
|
// Integrate multiple joins (nested).
|
|
// <join1-Type>
|
|
// (<join1-Table>
|
|
// <join2-Type>
|
|
// (<join2-Table> <join3-Type> <join3-Table> ON <join3-Filter>)
|
|
// ON <join2-Filter>)
|
|
// ON <join1-Filter>
|
|
//---------------------------------------------------------------
|
|
// Sage doesn't use 'AS' to alias tables.
|
|
String tableAliasOperator = (DbGlobalDbms.Type == DbmsType.Sage ? " " : " AS ");
|
|
String joinType = GetJoinType(Type);
|
|
String joinTable = Table.Concat(tableAliasOperator, false);
|
|
String subJoinData = String.Empty;
|
|
foreach (var subJoin in SubJoins)
|
|
{
|
|
// Add another bracket in front of the table:
|
|
joinTable = $"({joinTable}";
|
|
// Add the sub join.
|
|
subJoinData += $"{subJoin.BuildJoin("")})";
|
|
}
|
|
//---------------------------------------------------------------
|
|
// Build this join's list of filter conditions.
|
|
// Take care not to add too many embedded brackets - confuses Access etc.
|
|
// [Original version: lFilter.Concat (" AND ", "(", ")", true, true)].
|
|
String filter = String.Empty;
|
|
foreach (var rawFilterClause in Filters)
|
|
{
|
|
// Retrieve the next filter clause.
|
|
String filterClause = rawFilterClause.Trim();
|
|
if (filterClause.IsEmpty()) continue;
|
|
// Embed the clause in brackets if it contains at least one 'OR' operator.
|
|
if (filterClause.IndexOf(" OR ", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
filterClause = $"({filterClause})";
|
|
filter += (!filter.IsEmpty() ? " AND " : "") + filterClause;
|
|
}
|
|
// Surround the entire filter in brackets (if not already done).
|
|
if (!StringUtil.IsBlank(filter) && filter.FindMatchingBrace(0) != filter.Length)
|
|
filter = $"({filter})";
|
|
//---------------------------------------------------------------
|
|
// Build the join.
|
|
if (enclosedJoin.IsEmpty() && subJoinData.IsEmpty())
|
|
{
|
|
// Simple join syntax.
|
|
// <Join-Type> <Join-Table> ON <Join-Filter>
|
|
joinData = $"{joinType} {joinTable}";
|
|
}
|
|
else
|
|
{
|
|
// More complex join syntax - nested joins.
|
|
// <Join-Type> (<Join-Table> <NestedJoin>) ON <Join-Filter>
|
|
// Bracket the table, enclosing any enclosed or subordinate joins.
|
|
joinData = $"{joinType} ({joinTable}";
|
|
if (!subJoinData.IsEmpty())
|
|
joinData += $" {subJoinData}";
|
|
if (!enclosedJoin.IsEmpty())
|
|
joinData += $" {enclosedJoin}";
|
|
joinData += ")";
|
|
}
|
|
//---------------------------------------------------------------
|
|
// Add the join filter.
|
|
if (Type != DbJoinType.OuterApply)
|
|
joinData += $" ON {filter}";
|
|
else if (!StringUtil.IsBlank(filter))
|
|
SaLogger.Warn($"{SysUtil.Fn} Filter found on 'Outer Apply' join: '{filter}'.");
|
|
//---------------------------------------------------------------
|
|
return joinData;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Debugging.
|
|
public void Dump()
|
|
{
|
|
SaLogger.Debug($"{SysUtil.Fn}");
|
|
SaLogger.Debug($"\tType = {GetJoinType(Type)}");
|
|
SaLogger.Debug($"\tTable = {Table.Concat(" AS ")}");
|
|
SaLogger.Debug($"\tFilters = {Filters.Concat("", "\t\t", "\n")}");
|
|
if (SubJoins.Count > 0)
|
|
{
|
|
}
|
|
SaLogger.Debug($"\tJoin = {BuildJoin("")}");
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
|
|
//=======================================================================
|
|
public static class DbQueryJoinList
|
|
{
|
|
//-------------------------------------------------------------------
|
|
// List extensions.
|
|
public static String BuildJoins(this List<DbQueryJoin> joins, String primaryTable)
|
|
{
|
|
String joinData = primaryTable;
|
|
if (joins.Count > 0)
|
|
{
|
|
//-----------------------------------------------------------
|
|
// Integrate multiple joins (nested).
|
|
// <join1-Type>
|
|
// (<join1-Table>
|
|
// <join2-Type>
|
|
// (<join2-Table> <join3-Type> <join3-Table> ON <join3-Filter>)
|
|
// ON <join2-Filter>)
|
|
// ON <join1-Filter>
|
|
//-----------------------------------------------------------
|
|
bool bBracketFromFirstToLast = true;
|
|
if (bBracketFromFirstToLast)
|
|
{
|
|
// Bracketing from the first through to the last causes the
|
|
// Main table name to be included in the brackets.
|
|
// This appears to work for complex joins (multiple tables)
|
|
// but may not be 100% compliant with the SQL specification.
|
|
int joinCount = 0;
|
|
foreach (var join in joins)
|
|
{
|
|
// Add the next join in the list.
|
|
if (++joinCount > 1 && !joinData.IsEmpty()) joinData = $"({joinData})";
|
|
joinData = $"{joinData} {join.BuildJoin("")}";
|
|
joinData.Trim();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Jul-04 Attempt to organise joins with innermost bracketed.
|
|
// Start from the end of the list and work backwards.
|
|
// NB Fails with multiple joins - check SQL specification!
|
|
// Start with the last join and roll up towards the first.
|
|
joinData = String.Empty;
|
|
foreach (var join in joins)
|
|
{
|
|
// Add the next join in the list.
|
|
joinData = join.BuildJoin(joinData);
|
|
joinData.Trim();
|
|
}
|
|
joinData = $"{primaryTable} {joinData}";
|
|
}
|
|
}
|
|
return joinData;
|
|
}
|
|
//-------------------------------------------------------------------
|
|
}
|
|
|
|
//=======================================================================
|
|
public class DbQuery
|
|
{
|
|
//-------------------------------------------------------------------
|
|
// Results paging.
|
|
public class Paging
|
|
{
|
|
public uint PageNo { get; set; } = 0;
|
|
public ushort PageLength { get; set; } = 0;
|
|
public virtual bool Paged { get { return PageNo > 0 && PageLength > 0; } }
|
|
public void Set(uint pageNo, ushort pageLength)
|
|
{
|
|
this.PageNo = pageNo;
|
|
this.PageLength = pageLength;
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Data members.
|
|
|
|
//-------------------------------------------------------------------
|
|
// Construction.
|
|
public DbQuery(DbmsType dbms)
|
|
{
|
|
DbGlobalDbms.Type = dbms;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Properties.
|
|
public bool DistinctRows { get; set; } = false;
|
|
public uint RowLimit { get; set; } = 0;
|
|
public List<StringPair> Tables { get; } = new List<StringPair>();
|
|
public List<StringPair> Columns { get; } = new List<StringPair>(); /* s1 = qualified column name, s2 = optional alias. */
|
|
public List<String> Filters { get; } = new List<string>();
|
|
public List<String> GroupBy { get; } = new List<string>();
|
|
public List<String> OrderBy { get; } = new List<string>();
|
|
public List<DbQueryJoin> Joins { get; } = new List<DbQueryJoin>();
|
|
/// <summary>Date value used as alternative to Null on database (ref. C++ ODBC defaults).</summary>
|
|
public static DateTime NullDate { get; } = new DateTime(1970, 1, 1);
|
|
|
|
//-------------------------------------------------------------------
|
|
// S t r i n g V a l u e F o r m a t t i n g
|
|
//-------------------------------------------------------------------
|
|
/// <summary>
|
|
/// Format the value so that it can be used in a conditional clause.
|
|
/// <para>- single quotes are duplicated.</para>
|
|
/// </summary>
|
|
public static String FormatForCondition(String value)
|
|
{
|
|
return StringUtil.EmptyOnNull(value).Replace("'", "''");
|
|
}
|
|
/// <summary>
|
|
/// Format the specified string value so that it can be used in a 'LIKE' clause.
|
|
/// <para>- single quotes are duplicated</para>
|
|
/// <para>- open square bracket ('[') is enclosed in square brackets ('[[]')</para>
|
|
/// <para>- percent ('%') is enclosed in square brackets ('[%]') unless wildcards allowed.</para>
|
|
/// <para>- asterisk ('*') converted to percent ('%') if wildcards allowed.</para>
|
|
/// </summary>
|
|
public static String FormatForLike(String value, bool allowWildcards = false)
|
|
{
|
|
value = StringUtil.EmptyOnNull(value).Replace("'", "''").Replace("[", "[[]");
|
|
if (allowWildcards)
|
|
{
|
|
value = value.Replace("*", "%");
|
|
}
|
|
else
|
|
{
|
|
value = value.Replace("%", "[%]");
|
|
}
|
|
return value;
|
|
}
|
|
/// <summary>
|
|
/// Format the list of values so that they it can be used in an 'In' clause.
|
|
/// <para>- Ignore blanks (if instructed), remove duplicates & pass string values through 'FormatForCondition'.</para>
|
|
/// <para>- Format as: ('Value1','Value2','Value3') if string data, else (Value1,Value2,Value3) etc.</para>
|
|
/// <para>- Returns empty string if list is empty.</para>
|
|
/// </summary>
|
|
public static String FormatForIn(IEnumerable<String> rawValues, bool stringData = true, bool ignoreBlanks = true)
|
|
{
|
|
String result = String.Empty;
|
|
List<String> values = new List<String>();
|
|
foreach (var rawValue in rawValues)
|
|
{
|
|
String value = rawValue;
|
|
if (!rawValue.IsEmpty() || !ignoreBlanks)
|
|
{
|
|
if (stringData)
|
|
value = $"'{FormatForCondition(value)}'";
|
|
else value.Trim(); // Non string data can be cleansed.
|
|
values.Add(value);
|
|
}
|
|
}
|
|
values.RemoveDuplicatesCI();
|
|
if (values.Count > 0)
|
|
result = $"({values.Concat(",")})";
|
|
return result;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// D a t e V a l u e F o r m a t t i n g
|
|
//-------------------------------------------------------------------
|
|
public static String GetDateDelim()
|
|
{
|
|
String delim = "#"; // MS Access only?
|
|
if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
delim = "'"; // MS SQL Server.
|
|
return delim;
|
|
}
|
|
public static String FormatDateForCondition(DateTime date, bool delimit = true, bool includeMidnight = false)
|
|
{
|
|
String value = DateUtil.Format(date, "dd-MMM-yyyy");
|
|
if (includeMidnight)
|
|
value += " 00:00:00";
|
|
if (delimit)
|
|
{
|
|
String delim = GetDateDelim();
|
|
value = delim + value + delim;
|
|
}
|
|
return value;
|
|
}
|
|
public static String FormatDateTimeForCondition(DateTime dateTime, bool delimit = true, bool precise = false)
|
|
{
|
|
String value = DateUtil.Format(dateTime, "yyyy-MM-dd HH:mm:ss" + (precise ? ".fff" : ""));
|
|
if (delimit)
|
|
{
|
|
String delim = GetDateDelim();
|
|
value = delim + value + delim;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
public static String FormatTimeForCondition(DateTime time, bool delimit = true, bool zeroSeconds = false)
|
|
{
|
|
String value = String.Empty;
|
|
if (zeroSeconds)
|
|
value = DateUtil.Format(time, "HH:mm:00");
|
|
else
|
|
value = DateUtil.Format(time, "HH:mm:ss");
|
|
if (delimit)
|
|
{
|
|
String delim = GetDateDelim();
|
|
value = delim + value + delim;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// F o r m a t F o r F i l t e r
|
|
//-------------------------------------------------------------------
|
|
public static String FormatNullValueFilter(String expression, bool excludeNulls)
|
|
{
|
|
String clause = String.Empty;
|
|
expression = StringUtil.EmptyOnNull(expression).Trim();
|
|
if (!expression.IsEmpty())
|
|
{
|
|
// Surround the expression in brackets if not present.
|
|
if (expression.Left(1) != "(")
|
|
expression = $"({expression})";
|
|
// Logic to include or exclude null values.
|
|
if (excludeNulls)
|
|
{
|
|
// Explicitly exclude Null values.
|
|
//if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
// sClause = "(CASE WHEN "+sExpr+" IS NULL THEN 0 ELSE 1 END) = 1";
|
|
//else
|
|
clause = expression + " IS NOT NULL";
|
|
}
|
|
else
|
|
{
|
|
// Explicitly include only Null values.
|
|
//if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
// sClause = "(CASE WHEN "+sExpr+" IS NULL THEN 0 ELSE 1 END) = 0";
|
|
//else
|
|
clause = expression + " IS NULL";
|
|
}
|
|
}
|
|
return clause;
|
|
}
|
|
// Construct 'Order By' clauses, allowing direction (ASC/DESC) and whether
|
|
// to demote NULL values (push to end of sequence) into account.
|
|
public static List<String> FormatForOrderBy(String expression, bool ascending, DbOrderByNullMode nullMode = DbOrderByNullMode.Default, bool treatZeroAsNull = false)
|
|
{
|
|
List<String> orderBy = new List<string>();
|
|
expression = StringUtil.EmptyOnNull(expression).Trim();
|
|
if (!expression.IsEmpty())
|
|
{
|
|
// Surround the expression in brackets if not present.
|
|
if (expression.Left(1) != "(")
|
|
expression = $"({expression})";
|
|
// Logic to promote null values to start of sequence or demote null values to the end of the sequence.
|
|
String nullsClause = String.Empty;
|
|
if (nullMode == DbOrderByNullMode.PromoteNulls || nullMode == DbOrderByNullMode.DatePromoteNulls)
|
|
{
|
|
if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
{
|
|
// Treat NULLs before other values.
|
|
List<String> nullsFilters = new List<String>();
|
|
nullsFilters.Add($"{expression} IS NULL");
|
|
if (treatZeroAsNull)
|
|
nullsFilters.Add($"{expression}=0");
|
|
if (nullMode == DbOrderByNullMode.DatePromoteNulls)
|
|
nullsFilters.Add($"{expression}<={FormatDateForCondition(NullDate)}");
|
|
nullsClause = $"(CASE WHEN {nullsFilters.Concat(" OR ", "(", ")")} THEN 0 ELSE 1 END)";
|
|
}
|
|
else
|
|
nullsClause = $"{expression} IS NULL";
|
|
}
|
|
else if (nullMode == DbOrderByNullMode.DemoteNulls || nullMode == DbOrderByNullMode.DateDemoteNulls)
|
|
{
|
|
if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
{
|
|
// Treat NULLs after other values.
|
|
List<String> nullsFilters = new List<string>();
|
|
nullsFilters.Add(expression + " IS NULL");
|
|
if (treatZeroAsNull)
|
|
nullsFilters.Add(expression + "=0");
|
|
if (nullMode == DbOrderByNullMode.DateDemoteNulls)
|
|
nullsFilters.Add($"{expression}<={FormatDateForCondition(NullDate)}");
|
|
nullsClause = $"(CASE WHEN {nullsFilters.Concat(" OR ", "(", ")")} THEN 1 ELSE 0 END)";
|
|
}
|
|
else
|
|
nullsClause = $"{expression} IS NOT NULL";
|
|
}
|
|
if (!nullsClause.IsEmpty()) orderBy.Add(nullsClause);
|
|
// Main clause for this expression.
|
|
orderBy.Add($"{expression} {(ascending ? "ASC" : "DESC")}");
|
|
}
|
|
return orderBy;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// S Q L F u n c t i o n F o r m a t t i n g
|
|
//-------------------------------------------------------------------
|
|
// NB Many functions depend on the DBMS in use.
|
|
// Default versions are for Access/SQL.
|
|
public static String FormatFn_Len(String column)
|
|
{
|
|
if (String.IsNullOrEmpty(column)) return "''";
|
|
// MS Access.
|
|
return "Len(" + column + ")";
|
|
}
|
|
public static String FormatFn_Left(String column, int length)
|
|
{
|
|
if (String.IsNullOrEmpty(column) || length < 1) return "''";
|
|
// MS Access.
|
|
return "Left(" + column + "," + length.ToString() + ")";
|
|
}
|
|
public static String FormatFn_Right(String column, int length)
|
|
{
|
|
if (String.IsNullOrEmpty(column) || length < 1) return "''";
|
|
// MS Access.
|
|
return "Right(" + column + "," + length.ToString() + ")";
|
|
}
|
|
public static String FormatFn_TimeValue(String expression)
|
|
{
|
|
if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
{
|
|
// Convert with Date/Time values.
|
|
// Param 3 - Date/Time format.
|
|
// 1 = mm/dd/yy
|
|
// 2 = yy.mm.dd
|
|
// 3 = dd/mm/yy
|
|
// 4 = dd.mm.yy
|
|
// 5 = dd-mm-yy
|
|
// 6 = dd mmm yy
|
|
// 7 = mmm dd, yy
|
|
// 8 = hh:mm:ss
|
|
// 9 = mmm dd yyyy hh:mm:ss:000AP
|
|
return "Convert(VarChar(10)," + expression + ",8)"; // CONVERT(varchar(10), DateClosed, 8)
|
|
}
|
|
return "TimeValue(" + expression + ")";
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// N u m e r i c F u n c t i o n s
|
|
//-------------------------------------------------------------------
|
|
public static String FormatFn_Abs(String sExpr)
|
|
{
|
|
if (sExpr.IsEmpty()) return "''";
|
|
return "Abs(" + sExpr + ")";
|
|
}
|
|
|
|
public static String FormatFn_Round(String expression, int decimals = 0)
|
|
{
|
|
if (String.IsNullOrEmpty(expression) || decimals < 0) return "''";
|
|
if (DbGlobalDbms.Type == DbmsType.Access)
|
|
{
|
|
// MS Access.
|
|
// NB Uses the 'round-to-even' logic - 0.5 values go to the even whole value rather than always up.
|
|
// Solution to get it behave consistently - add a fraction to make it 0.5001.
|
|
return "Round(" + expression + ((long)(10 ^ -(decimals + 2))).ToString() + "," + decimals.ToString() + ")";
|
|
}
|
|
return "Round(" + expression + "," + decimals.ToString() + ")";
|
|
}
|
|
|
|
public static String FormatFn_Truncate(String expression, int decimals = 0)
|
|
{
|
|
if (String.IsNullOrEmpty(expression) || decimals < 0) return "''";
|
|
if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
return "Round(" + expression + "," + decimals.ToString() + ",1)";
|
|
if (DbGlobalDbms.Type == DbmsType.Access)
|
|
// MS Access.
|
|
if (decimals > 0)
|
|
{
|
|
String factor = ((long)(10 ^ decimals)).ToString();
|
|
return "Int(" + expression + "*" + factor + "," + decimals.ToString() + ")/" + factor;
|
|
}
|
|
return "Int(" + expression + "," + decimals.ToString() + ")";
|
|
}
|
|
|
|
public static String FormatFn_FormatNumber(String expression, int decimals)
|
|
{
|
|
if (String.IsNullOrEmpty(expression) || decimals < 0) return "''";
|
|
// MS Access.
|
|
return "FormatNumber(" + expression + "," + decimals.ToString() + ")";
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// D a t e D i f f e r e n c e
|
|
//-------------------------------------------------------------------
|
|
// Date difference: eg. DateDiff('n',Col1,Col2).
|
|
public static String FormatFn_DateDiff(DbDateDiffInterval dateInterval, String column1, String column2)
|
|
{
|
|
if (column1.IsEmpty() || column2.IsEmpty()) return "''";
|
|
String interval = String.Empty;
|
|
if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
{
|
|
// SQL Server.
|
|
switch (dateInterval)
|
|
{
|
|
// NB No delimiter.
|
|
case DbDateDiffInterval.Year: interval = "yyyy"; break;
|
|
case DbDateDiffInterval.Quarter: interval = "q"; break;
|
|
case DbDateDiffInterval.Month: interval = "m"; break;
|
|
case DbDateDiffInterval.DayOfYear: interval = "y"; break;
|
|
case DbDateDiffInterval.Day: interval = "d"; break;
|
|
//case Weekday: interval = "w"; break; // Not supported by SQL Server.
|
|
case DbDateDiffInterval.Week: interval = "ww"; break;
|
|
case DbDateDiffInterval.Hour: interval = "h"; break;
|
|
case DbDateDiffInterval.Minute: interval = "n"; break;
|
|
case DbDateDiffInterval.Second: interval = "s"; break;
|
|
//case MilliSecond: interval = "ms"; break; // Not yet defined.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// MS Access.
|
|
switch (dateInterval)
|
|
{
|
|
case DbDateDiffInterval.Year: interval = "'yyyy'"; break;
|
|
case DbDateDiffInterval.Quarter: interval = "'q'"; break;
|
|
case DbDateDiffInterval.Month: interval = "'m'"; break;
|
|
case DbDateDiffInterval.DayOfYear: interval = "'y'"; break;
|
|
case DbDateDiffInterval.Day: interval = "'d'"; break;
|
|
case DbDateDiffInterval.Weekday: interval = "'w'"; break;
|
|
case DbDateDiffInterval.Week: interval = "'ww'"; break;
|
|
case DbDateDiffInterval.Hour: interval = "'h'"; break;
|
|
case DbDateDiffInterval.Minute: interval = "'n'"; break;
|
|
case DbDateDiffInterval.Second: interval = "'s'"; break;
|
|
}
|
|
if (interval.IsEmpty()) return "''";
|
|
}
|
|
return $"DateDiff({interval},{column1},{column2})";
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// B i t M a p p e d V a l u e s
|
|
//-------------------------------------------------------------------
|
|
public static String BuildBitMappedValue(String column, ushort bit, bool match)
|
|
{
|
|
// Filter clause to test for specific bit set in field.
|
|
String clause = String.Empty;
|
|
if (!String.IsNullOrEmpty(column))
|
|
{
|
|
if (bit > 0 && bit <= 32)
|
|
{
|
|
// The operation depends on whether the value should be matched or not.
|
|
String condition = (match ? "<>" : "=");
|
|
// Test is achieved by shifting the value so as to drop the target bit, shift back one bit
|
|
// and identify those value that then do not match the original value shifted by one place less.
|
|
// Example match: abcdefgh, Test bit 3: abcde0 <> abcdef
|
|
clause = "(" + FormatFn_Truncate(column + "/" + Math.Pow(2, bit).ToString()) + "*2)" + condition + FormatFn_Truncate(column + "/" + Math.Pow(2, bit - 1).ToString());
|
|
if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
clause = "(Case When " + clause + " Then 1 Else 0 End)";
|
|
}
|
|
#if DEBUG
|
|
else
|
|
{
|
|
SaLogger.Debug($"BuildBitMappedFilter - Invalid bit ({bit}).");
|
|
}
|
|
#endif
|
|
}
|
|
#if DEBUG
|
|
else
|
|
{
|
|
SaLogger.Debug("BuildBitMappedFilter - NULL column name.");
|
|
}
|
|
#endif
|
|
return clause;
|
|
}
|
|
|
|
public static String BuildBitMappedFilter(String column, ushort bit, bool match)
|
|
{
|
|
String clause = BuildBitMappedValue(column, bit, match);
|
|
if (!clause.IsEmpty())
|
|
{
|
|
if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
clause = $"({clause} <> 0)";
|
|
}
|
|
return clause;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// S o r t C o l u m n s
|
|
//-------------------------------------------------------------------
|
|
public static String GetRandomOrderSortColumn()
|
|
{
|
|
return GetRandomOrderSortColumn(DbGlobalDbms.Type);
|
|
}
|
|
public static String GetRandomOrderSortColumn(DbmsType dbms)
|
|
{
|
|
if (dbms == DbmsType.MsSql)
|
|
return "NewId()";
|
|
// ToDo: Random sort order by DBMS.
|
|
return "Random()";
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// A t t r i b u t e s
|
|
//-------------------------------------------------------------------
|
|
public void Clear()
|
|
{
|
|
Tables.Clear();
|
|
Columns.Clear();
|
|
Filters.Clear();
|
|
GroupBy.Clear();
|
|
OrderBy.Clear();
|
|
Joins.Clear();
|
|
}
|
|
public bool IsValid()
|
|
{
|
|
return (Tables.Count > 0);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// B u i l d Q u e r y
|
|
//-------------------------------------------------------------------
|
|
public String BuildQuery(Paging paging = null)
|
|
{
|
|
if (Tables.Count < 1) return "";
|
|
|
|
//---------------------------------------------------------------------
|
|
// Start with the column names.
|
|
String query = "SELECT";
|
|
if (DistinctRows) query += " DISTINCT";
|
|
if (RowLimit > 0) query += $" TOP {RowLimit.ToString()}";
|
|
if (Columns.Count < 1) query += " *";
|
|
else query += $" {Columns.Concat(" AS ", false).Concat(",")}";
|
|
|
|
//---------------------------------------------------------------------
|
|
// Add the tables and joins.
|
|
String tableAliasOperator = DbGlobalDbms.Type == DbmsType.Sage ? " " : " AS ";
|
|
if (Tables.Count > 1 || Joins.Count < 1)
|
|
{
|
|
query += $" FROM {Tables.Concat(tableAliasOperator, false).Concat(",")} {Joins.BuildJoins("")}";
|
|
}
|
|
else if (Tables.Count == 1)
|
|
{
|
|
// Single Table name is bracketed with multiple joins.
|
|
query += $" FROM {Joins.BuildJoins(Tables.Concat(tableAliasOperator, false)[0])}";
|
|
}
|
|
query.TrimEnd(' ');
|
|
|
|
//---------------------------------------------------------------------
|
|
// Filters.
|
|
String filter = Filters.Concat(" AND ", "(", ")", true, true);
|
|
// Jan2019 Not required: if (Filters.Count > 1) filter = "(" + filter + ")";
|
|
if (!filter.IsEmpty()) query += $" WHERE {filter}";
|
|
|
|
// Groupings.
|
|
String groupByClause = GroupBy.Concat(",", "", "", true, true);
|
|
if (!groupByClause.IsEmpty()) query += $" GROUP BY {groupByClause}";
|
|
|
|
// Sort sequence.
|
|
String orderByClause = OrderBy.Concat(",", "", "", true, true);
|
|
if (!orderByClause.IsEmpty()) query += $" ORDER BY {orderByClause}";
|
|
|
|
// Paging.
|
|
if (paging != null && paging.Paged)
|
|
{
|
|
// Must have an 'Order By' clause to use 'Offset ... Fetch Next ...'
|
|
if (orderByClause.IsEmpty())
|
|
throw new NotSupportedException("Query paging requires sort order to be specified");
|
|
if (DbGlobalDbms.Type == DbmsType.MsSql)
|
|
query += $" OFFSET {((paging.PageNo - 1) * paging.PageLength)} ROWS FETCH NEXT {paging.PageLength} ROWS ONLY";
|
|
else throw new NotSupportedException($"Query paging not supported for {DbGlobalDbms.Type}");
|
|
}
|
|
|
|
//---------------------------------------------------------------------
|
|
#if DEBUG
|
|
bool bDisplayDebugMsg = false;
|
|
if (bDisplayDebugMsg)
|
|
SaLogger.Debug($"Query: {query}");
|
|
#endif
|
|
|
|
//---------------------------------------------------------------------
|
|
return query;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
|
|
//=======================================================================
|
|
}
|