sozsoft-platform/api/src/Sozsoft.Platform.Domain/Queries/SelectQueryManager.cs

1040 lines
40 KiB
C#
Raw Normal View History

2026-02-24 20:44:16 +00:00
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.Json;
2026-07-10 09:22:54 +00:00
using System.Threading.Tasks;
using Microsoft.Extensions.Localization;
2026-02-24 20:44:16 +00:00
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.Enums;
using Sozsoft.Platform.Extensions;
using Sozsoft.Platform.ListForms;
using Sozsoft.Platform.Localization;
using Sozsoft.Platform.OrganizationUnits;
2026-10-05 12:58:59 +00:00
using Volo.Abp;
2026-02-24 20:44:16 +00:00
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings;
using Volo.Abp.Users;
namespace Sozsoft.Platform.Queries;
public interface ISelectQueryManager
{
List<SelectField> SelectFields { get; }
string SelectQuery { get; }
Dictionary<string, object> SelectQueryParameters { get; }
string TotalCountQuery { get; }
string GroupQuery { get; }
string ChartQuery { get; }
public string DeleteQuery { get; }
List<(int Index, string Field, string SelectExpr, string Sort, bool IsExpanded, string GroupInterval)> GroupTuples { get; }
List<(int Index, string Field, string SelectExpr, string SummaryType)> GroupSummaryTuples { get; }
List<string> SummaryQueries { get; }
public bool IsAppliedGridFilter { get; }
public bool IsAppliedServerFilter { get; }
2026-07-10 09:22:54 +00:00
Task PrepareQueriesAsync(ListForm listform,
List<ListFormField> listFormFields,
DataSourceTypeEnum dataSourceType,
List<ListFormCustomization> listFormCustomizations = null,
QueryParameters queryParams = null);
2026-02-24 20:44:16 +00:00
}
public class SelectField
{
public SelectField() { }
public SelectField(ListFormField item, string baseTableName)
{
FieldName = item.FieldName;
if (!string.IsNullOrEmpty(item.JoinTableJson))
JoinOptions = JsonSerializer.Deserialize<JoinTable>(item.JoinTableJson);
if (JoinOptions != null && !string.IsNullOrWhiteSpace(JoinOptions.TableName))
{
if (!string.IsNullOrEmpty(JoinOptions.FieldNameAlias))
SelectFieldName = $"\"{JoinOptions.TableName}\".\"{JoinOptions.SelectFieldName}\" AS \"{JoinOptions.FieldNameAlias}\"";
else
SelectFieldName = $"\"{JoinOptions.TableName}\".\"{JoinOptions.SelectFieldName}\" AS \"{FieldName}\"";
JoinSql = $"{JoinOptions.JoinType} \"{JoinOptions.TableName}\" ON \"{JoinOptions.TableName}\".\"{JoinOptions.JoinFieldName}\" = \"{baseTableName}\".\"{JoinOptions.BaseTableJoinFieldName}\"";
// JOIN bolumunde birden fazla condition var ise
if (!string.IsNullOrEmpty(JoinOptions.JoinFieldName2))
{
JoinSql += $" {JoinOptions.JoinCondition2} \"{JoinOptions.JoinFieldName2}\" = {JoinOptions.BaseTableJoinFieldName2_OrValue}";
}
IsJoined = true;
}
else
{
SelectFieldName = $"\"{baseTableName}\".\"{this.FieldName}\" AS \"{FieldName}\"";
IsJoined = false;
}
}
public string FieldName { get; set; } // UserName
public string SelectFieldName { get; set; } // ListForm.UserName as UserName
public bool IsJoined { get; set; }
public JoinTable JoinOptions { get; set; }
public string JoinSql { get; set; }
}
public class SelectQueryManager : PlatformDomainService, ISelectQueryManager
{
private string SelectCommand { get; set; }
private string TableName { get; set; }
private string From { get { return SelectCommandType == SelectCommandTypeEnum.Query ? $"({SelectCommand})" : SelectCommand; } }
private SelectCommandTypeEnum SelectCommandType { get; set; }
private string KeyFieldName { get; set; }
2026-09-30 20:52:33 +00:00
private DbType KeyFieldDbSourceType { get; set; }
2026-02-24 20:44:16 +00:00
private List<string> JoinParts { get; set; }
private List<string> WhereParts { get; set; }
private List<string> SortParts { get; set; }
private DataSourceTypeEnum DataSourceType { get; set; }
public List<SelectField> SelectFields { get; private set; }
public string SelectQuery { get; private set; }
public Dictionary<string, object> SelectQueryParameters { get; private set; }
public string TotalCountQuery { get; private set; }
public string GroupQuery { get; private set; }
public string DeleteQuery { get; private set; }
public string ChartQuery { get; private set; }
public List<(int Index, string Field, string SelectExpr, string Sort, bool IsExpanded, string GroupInterval)> GroupTuples { get; private set; } = [];
public List<(int Index, string Field, string SelectExpr, string SummaryType)> GroupSummaryTuples { get; private set; } = [];
public List<string> SummaryQueries { get; private set; }
public bool IsAppliedGridFilter { get; private set; }
public bool IsAppliedServerFilter { get; private set; }
public IPlatformOuRepository OuRepository { get; }
public IRepository<BranchUsers, Guid> BranchUsersRepository { get; }
public DefaultValueHelper DefaultValueHelper { get; }
public SelectQueryManager(
ISettingProvider settingProvider,
IStringLocalizer<PlatformResource> localizer,
ICurrentUser currentUser,
IObjectMapper objectMapper,
IListFormAuthorizationManager authManager,
IPlatformOuRepository ouRepository,
IRepository<BranchUsers, Guid> branchUsersRepository,
DefaultValueHelper defaultValueHelper)
: base(settingProvider, localizer, currentUser, objectMapper, authManager)
{
SelectFields = [];
JoinParts = [];
WhereParts = [];
SortParts = [];
SummaryQueries = [];
OuRepository = ouRepository;
BranchUsersRepository = branchUsersRepository;
DefaultValueHelper = defaultValueHelper;
}
2026-07-10 09:22:54 +00:00
public async Task PrepareQueriesAsync(ListForm listform,
List<ListFormField> listFormFields,
DataSourceTypeEnum dataSourceType,
List<ListFormCustomization> listFormCustomizations = null,
QueryParameters queryParams = null)
2026-02-24 20:44:16 +00:00
{
ResetQueryState();
2026-02-24 20:44:16 +00:00
if (listform == null)
{
return;
}
int take = queryParams?.Take > 0
? queryParams.Take
: listform.PageSize > 0
? listform.PageSize
: PlatformConsts.GridOptions.RowsPerPage;
int skip = queryParams?.Skip ?? 0;
DataSourceType = dataSourceType;
SelectCommand = DefaultValueHelper.GetDefaultValue(listform.SelectCommand);
TableName = listform.TableName.IsNullOrEmpty() ? SelectCommand : listform.TableName;
KeyFieldName = listform.KeyFieldName;
2026-09-30 20:52:33 +00:00
KeyFieldDbSourceType = listform.KeyFieldDbSourceType;
2026-02-24 20:44:16 +00:00
SelectCommandType = listform.SelectCommandType;
var KeyField = new SelectField
{
FieldName = KeyFieldName,
SelectFieldName = $"\"{TableName}\".\"{KeyFieldName}\" AS \"{KeyFieldName}\"",
IsJoined = false
};
SelectFields.AddRange(GetSelectAndJoinFields(listFormFields));
if (!SelectFields.Any(a => a.FieldName == KeyField.FieldName))
{
SelectFields.Add(KeyField);
}
var joinParts = SelectFields.Where(c => !string.IsNullOrEmpty(c.JoinSql)).Select(c => c.JoinSql).Distinct();
JoinParts.AddRange(joinParts);
2026-07-10 09:22:54 +00:00
List<string> whereFields = await GetWhereFieldsAsync(listform, listFormFields, queryParams);
2026-02-24 20:44:16 +00:00
WhereParts.AddRange(whereFields);
List<string> sortFields = GetSortFields(listform, listFormFields, queryParams?.Sort);
SortParts.AddRange(sortFields);
SelectQuery = GetSelectString();
SetJoinStringFromCustomizations(listFormCustomizations);
SelectQuery += GetJoinString();
SetWhereStringFromCustomizations(listFormCustomizations);
SelectQuery += GetWhereString();
SelectQuery += GetSortString();
// queryParams null ise grid olusturmak icin gelmistir, paging yapma
if (queryParams != null && queryParams.Take > 0)
{
SelectQuery += GetPagingString(take, skip);
}
TotalCountQuery = GetTotalCountQuery();
GroupQuery = GetGroupQuery(listFormFields, queryParams);
DeleteQuery = GetDeleteQuery(queryParams);
ChartQuery = GetChartQuery(listform, queryParams);
#region Total Summary Queries
if (queryParams != null && !queryParams.TotalSummary.IsNullOrWhiteSpace())
{
var items = new List<object>();
var summaries = queryParams.TotalSummary.Split(PlatformConsts.MultiValueDelimiter);
for (int i = 0; i < summaries.Length; i++)
{
var summary = summaries[i].Split(' ');
2026-10-05 12:58:59 +00:00
// Toplam türü SQL fonksiyon adı olarak yazılır; yalnızca bilinen beş tür kabul edilir.
if (summary.Length != 2 || !SummaryTypes.Contains(summary[1]))
{
continue;
}
var summmaryType = summary[1];
var fieldName = ConvertAliasToFieldName(summary[0], listFormFields);
if (fieldName == UnknownFieldName)
2026-02-24 20:44:16 +00:00
{
continue;
}
SummaryQueries.Add(GetSummaryQuery(fieldName, summmaryType));
}
}
#endregion
}
private void ResetQueryState()
{
SelectCommand = null;
TableName = null;
KeyFieldName = null;
2026-09-30 20:52:33 +00:00
KeyFieldDbSourceType = default;
SelectFields = [];
JoinParts = [];
WhereParts = [];
SortParts = [];
SelectQuery = null;
SelectQueryParameters = [];
TotalCountQuery = null;
GroupQuery = null;
DeleteQuery = null;
ChartQuery = null;
GroupTuples = [];
GroupSummaryTuples = [];
SummaryQueries = [];
IsAppliedGridFilter = false;
IsAppliedServerFilter = false;
}
2026-02-24 20:44:16 +00:00
private List<SelectField> GetSelectAndJoinFields(List<ListFormField> listFormFields)
{
List<SelectField> selectFields = [];
foreach (var itemField in listFormFields.OrderBy(c => c.JoinTableJson))
{
2026-05-01 15:23:14 +00:00
if (!itemField.IsActive.HasValue || !itemField.IsActive.Value)
2026-02-24 20:44:16 +00:00
{
continue;
}
selectFields.Add(new(itemField, TableName));
}
return selectFields;
}
/// <summary> Sadece join li alanlar icin gercek SqlField bigisini verir
/// </summary>
/// <param name="filterName"></param>
/// <param name="listFormFields"></param>
/// <returns></returns>
private string ConvertAliasToFieldName(string filterName, List<ListFormField> listFormFields, string groupInterval = null)
{
var format = groupInterval switch
{
"year" =>
DataSourceType == DataSourceTypeEnum.Mssql ? "DATEPART(YEAR, {0})" :
DataSourceType == DataSourceTypeEnum.Postgresql ? "DATE_PART('YEAR', {0})" : "",
"quarter" =>
DataSourceType == DataSourceTypeEnum.Mssql ? "DATEPART(QUARTER, {0})" :
DataSourceType == DataSourceTypeEnum.Postgresql ? "DATE_PART('QUARTER', {0})" : "",
"month" =>
DataSourceType == DataSourceTypeEnum.Mssql ? "DATEPART(MONTH, {0})" :
DataSourceType == DataSourceTypeEnum.Postgresql ? "DATE_PART('MONTH', {0})" : "",
"day" =>
DataSourceType == DataSourceTypeEnum.Mssql ? "DATEPART(DAY, {0})" :
DataSourceType == DataSourceTypeEnum.Postgresql ? "DATE_PART('DAY', {0})" : "",
"dayOfWeek" =>
DataSourceType == DataSourceTypeEnum.Mssql ? "DATEPART(DW, {0})" :
DataSourceType == DataSourceTypeEnum.Postgresql ? "DATE_PART('DOW', {0})" : "",
_ => "{0}"
};
var field = listFormFields.FirstOrDefault(a => a.FieldName == filterName && !string.IsNullOrWhiteSpace(a.JoinTableJson));
if (field is not null)
{
var jt = JsonSerializer.Deserialize<JoinTable>(field.JoinTableJson);
if (!jt.TableName.IsNullOrWhiteSpace())
{
return string.Format(format, $"\"{jt.TableName}\".\"{jt.SelectFieldName}\"");
}
}
2026-10-05 12:58:59 +00:00
// Ad istemciden gelir (toplam, grup, filtre); ekranda olmayan bir ad SQL'e hiç yazılmaz.
if (filterName != KeyFieldName && !listFormFields.Any(a => a.FieldName == filterName))
{
return UnknownFieldName;
}
2026-02-24 20:44:16 +00:00
return string.Format(format, $"\"{TableName}\".\"{filterName}\"");
}
2026-10-05 12:58:59 +00:00
private const string UnknownFieldName = "undefined";
private static readonly HashSet<string> SummaryTypes = new(StringComparer.OrdinalIgnoreCase)
{
"sum", "avg", "min", "max", "count",
};
2026-02-24 20:44:16 +00:00
private string GetSelectString()
{
2026-09-30 20:52:33 +00:00
return $"SELECT {string.Join(',', SelectFields.Select(c => $"{c.SelectFieldName}"))} FROM {From} AS \"{TableName}\"";
2026-02-24 20:44:16 +00:00
}
private string SetJoinStringFromCustomizations(List<ListFormCustomization> listFormCustomizations)
{
if (listFormCustomizations.IsNullOrEmpty())
{
return string.Empty;
}
var customizations = listFormCustomizations
.Where(a => a.CustomizationType == ListFormCustomizationTypeEnum.ServerJoin)
.Select(a => a.CustomizationData)
.ToList();
JoinParts.AddRange(customizations);
return string.Join(' ', customizations);
}
/// <summary>
/// WhereParts icerisine ListFormCustomization tanimindan gelen filtreleri ekler
/// </summary>
/// <param name="listFormCustomization"></param>
private void SetWhereStringFromCustomizations(List<ListFormCustomization> listFormCustomizations)
{
if (listFormCustomizations.IsNullOrEmpty())
{
return;
}
var customizations = listFormCustomizations
.Where(a => a.CustomizationType == ListFormCustomizationTypeEnum.ServerWhere)
.Select(a => a.CustomizationData)
.ToList();
foreach (var item in customizations)
{
if (WhereParts.Any())
{
WhereParts.Add("AND");
}
WhereParts.Add(DefaultValueHelper.GetDefaultValue(item));
IsAppliedServerFilter = true;
}
}
2026-07-10 09:22:54 +00:00
private async Task<List<string>> GetWhereFieldsAsync(ListForm listform, List<ListFormField> listFormFields, QueryParameters queryParams = null)
2026-02-24 20:44:16 +00:00
{
var whereParts = new List<string>();
if (!string.IsNullOrEmpty(queryParams?.Filter))
{
2026-10-05 12:58:59 +00:00
SelectQueryParameters = new Dictionary<string, object>();
filterParameterIndex = 0;
2026-02-24 20:44:16 +00:00
2026-10-05 12:58:59 +00:00
var clientFilter = BuildClientFilter(queryParams.Filter, listFormFields);
if (clientFilter != null)
{
// İstemci filtresi tek grup olarak paranteze alınır; aksi hâlde içindeki "or",
// ardından eklenen kiracı/şube/OU koşullarını devre dışı bırakırdı.
whereParts.Add($"({clientFilter})");
IsAppliedGridFilter = true;
2026-02-24 20:44:16 +00:00
}
}
if (!listform.DefaultFilter.IsNullOrWhiteSpace())
{
if (whereParts.Any())
{
whereParts.Add("AND");
}
whereParts.Add(DefaultValueHelper.GetDefaultValue(listform.DefaultFilter));
IsAppliedServerFilter = true;
}
if (PlatformConsts.IsMultiTenant && listform.IsTenant)
{
if (whereParts.Any())
{
whereParts.Add("AND");
}
if (CurrentTenant.IsAvailable)
{
whereParts.Add($"\"TenantId\" = '{CurrentTenant.Id}'");
}
else
{
whereParts.Add($"\"TenantId\" IS NULL");
2026-02-24 20:44:16 +00:00
}
}
if (listform.IsBranch)
{
if (whereParts.Any())
{
whereParts.Add("AND");
}
2026-07-10 09:22:54 +00:00
var ids = await BranchUsersRepository.GetListAsync((a) => a.UserId == CurrentUser.Id.Value);
2026-02-24 20:44:16 +00:00
if (ids.Count > 0)
{
whereParts.Add($"\"BranchId\" IN ({string.Join(",", ids.Select(a => $"'{a.BranchId}'"))})");
}
else
{
whereParts.Add($"\"BranchId\" = '{Guid.Empty}'");
}
}
if (listform.IsOrganizationUnit)
{
if (whereParts.Any())
{
whereParts.Add("AND");
}
2026-07-10 09:22:54 +00:00
var ids = await OuRepository.GetOrganizationUnitIdsWithChildren(CurrentUser.Id.Value);
2026-02-24 20:44:16 +00:00
if (ids.Count > 0)
{
whereParts.Add($"\"OrganizationUnitId\" IN ({string.Join(",", ids.Select(a => $"'{a}'"))})");
}
else
{
whereParts.Add($"\"OrganizationUnitId\" = '{Guid.Empty}'");
}
}
if (listform.WorkflowJson.IsNullOrWhiteSpace() == false)
{
var workflow = JsonSerializer.Deserialize<Workflow>(listform.WorkflowJson);
2026-07-16 14:01:18 +00:00
if (workflow != null && workflow.ApprovalIsFilterUserName)
{
if (whereParts.Any())
{
whereParts.Add("AND");
}
// Hem CurrentUserName alanı boş olan kayıtları
// hem de ApprovalUserFieldName alanı CurrentUserName'e eşit olan kayıtları getirmek istiyoruz,
// Boş olanları getirmemizin sebebi workflow start edebilmektir.
// İlk kayıt eklenince onaylayacak kişi atanmaz, böylece o kayıt onaysız olarak kalmaz ve workflow başlatılabilir olur.
whereParts.Add(GetWorkflowApproverFilter(workflow.ApprovalUserFieldName));
}
}
2026-02-24 20:44:16 +00:00
if (!whereParts.Any())
{
whereParts.Add("1 = 1");
}
return whereParts;
}
private string GetWorkflowApproverFilter(string approvalUserFieldName)
{
if (approvalUserFieldName.IsNullOrWhiteSpace())
{
return "1 = 1";
}
var field = $"\"{approvalUserFieldName}\"";
var identities = new[] { CurrentUser.UserName, CurrentUser.Email, CurrentUser.Name }
.Where(identity => !identity.IsNullOrWhiteSpace())
.Select(EscapeSqlLiteral)
.Distinct()
.ToList();
if (identities.Count == 0)
{
return $"({field} IS NULL OR {field} = '')";
}
var identityList = string.Join(",", identities.Select(identity => $"'{identity}'"));
var approverMatch = DataSourceType == DataSourceTypeEnum.Postgresql
? $"EXISTS (SELECT 1 FROM unnest(string_to_array(CAST({field} AS TEXT), '{PlatformConsts.MultiValueDelimiter}')) AS split(value) WHERE btrim(split.value) IN ({identityList}))"
: $"EXISTS (SELECT 1 FROM STRING_SPLIT(CAST({field} AS NVARCHAR(MAX)), N'{PlatformConsts.MultiValueDelimiter}') AS split WHERE LTRIM(RTRIM(split.value)) IN ({identityList}))";
return $"({approverMatch} OR {field} IS NULL OR {field} = '')";
}
private static string EscapeSqlLiteral(string value)
{
return (value ?? string.Empty).Replace("'", "''");
}
2026-10-05 12:58:59 +00:00
private const int MaxFilterDepth = 32;
private static readonly HashSet<string> FilterOperators = new(StringComparer.OrdinalIgnoreCase)
{
"=", "<>", "<", "<=", ">", ">=", "contains", "notcontains", "startswith", "endswith",
};
private int filterParameterIndex;
/// <summary>
/// DevExtreme filtre dizisini (<c>["Alan","=",değer]</c>, <c>[koşul,"and",koşul]</c>, <c>["!",koşul]</c>)
/// ağaç olarak çözer. Alan adı ekranın alanlarından, operatör beyaz listeden gelir; değer her zaman
/// parametredir. Metin olarak işlenmez: önceki ayrıştırıcı virgül ve parantezle böldüğü için değerin bir
/// kısmı ham SQL olarak sorguya ekleniyordu.
2026-02-24 20:44:16 +00:00
/// </summary>
2026-10-05 12:58:59 +00:00
private string BuildClientFilter(string filterJson, List<ListFormField> listFormFields)
{
JsonElement root;
try
{
using var document = JsonDocument.Parse(filterJson);
root = document.RootElement.Clone();
}
catch (JsonException)
{
throw new UserFriendlyException("Invalid filter.");
}
if (root.ValueKind != JsonValueKind.Array)
{
throw new UserFriendlyException("Invalid filter.");
}
return BuildFilterNode(root, listFormFields, 0);
}
private string BuildFilterNode(JsonElement node, List<ListFormField> listFormFields, int depth)
{
if (depth > MaxFilterDepth)
{
throw new UserFriendlyException("Invalid filter.");
}
var items = node.EnumerateArray().ToList();
if (items.Count == 0)
{
return null;
}
if (items.Count == 2 && IsFilterToken(items[0], "!") && items[1].ValueKind == JsonValueKind.Array)
{
var negated = BuildFilterNode(items[1], listFormFields, depth + 1);
return negated == null ? null : $"NOT ({negated})";
}
if (IsFilterCondition(items))
{
return items.Count == 2
? BuildFilterCondition(items[0].GetString(), "=", items[1], listFormFields)
: BuildFilterCondition(items[0].GetString(), items[1].GetString(), items[2], listFormFields);
}
// Grup: terimler ve aralarındaki bağlaçlar. Bağlaçsız ardışık terimler "and" sayılır (DevExtreme
// kuralı). UI bazen koşulu gruba düz ekler (["A","=",1,"and",[...]]); bu üçlü de koşul olarak okunur.
var parts = new List<string>();
string connector = null;
for (var i = 0; i < items.Count; i++)
{
var item = items[i];
string term;
if (IsFilterToken(item, "and") || IsFilterToken(item, "or"))
{
connector = item.GetString().ToUpperInvariant();
continue;
}
if (IsFilterToken(item, "!") && i + 1 < items.Count && items[i + 1].ValueKind == JsonValueKind.Array)
{
var negated = BuildFilterNode(items[++i], listFormFields, depth + 1);
term = negated == null ? null : $"NOT ({negated})";
}
else if (item.ValueKind == JsonValueKind.Array)
{
var group = BuildFilterNode(item, listFormFields, depth + 1);
term = group == null ? null : $"({group})";
}
else if (item.ValueKind == JsonValueKind.String
&& i + 2 < items.Count
&& items[i + 1].ValueKind == JsonValueKind.String)
{
term = BuildFilterCondition(item.GetString(), items[i + 1].GetString(), items[i + 2], listFormFields);
i += 2;
}
else
{
throw new UserFriendlyException("Invalid filter.");
}
if (term == null)
{
continue;
}
if (parts.Count > 0)
{
parts.Add(connector ?? "AND");
}
parts.Add(term);
connector = null;
}
return parts.Count == 0 ? null : string.Join(" ", parts);
}
private static bool IsFilterToken(JsonElement item, string token)
{
return item.ValueKind == JsonValueKind.String
&& string.Equals(item.GetString(), token, StringComparison.OrdinalIgnoreCase);
}
/// <summary><c>["Alan","op",değer]</c> ya da kısa biçim <c>["Alan",değer]</c>.</summary>
private static bool IsFilterCondition(List<JsonElement> items)
{
if (items[0].ValueKind != JsonValueKind.String
|| IsFilterToken(items[0], "!")
|| IsFilterToken(items[0], "and")
|| IsFilterToken(items[0], "or"))
{
return false;
}
return items.Count switch
{
2 => items[1].ValueKind != JsonValueKind.Array,
3 => items[1].ValueKind == JsonValueKind.String
&& !IsFilterToken(items[1], "and")
&& !IsFilterToken(items[1], "or"),
_ => false,
};
}
/// <summary>Tek koşulun SQL'i. Alan ekranda yoksa koşul etkisiz (<c>1 = 1</c>) yazılır.</summary>
private string BuildFilterCondition(string fieldName, string op, JsonElement value, List<ListFormField> listFormFields)
2026-02-24 20:44:16 +00:00
{
if (string.IsNullOrWhiteSpace(fieldName) || string.IsNullOrWhiteSpace(op))
{
2026-10-05 12:58:59 +00:00
return null;
2026-02-24 20:44:16 +00:00
}
2026-10-05 12:58:59 +00:00
var fieldValue = value.ValueKind switch
{
JsonValueKind.String => value.GetString(),
JsonValueKind.Number => value.GetRawText(),
JsonValueKind.True => "true",
JsonValueKind.False => "false",
JsonValueKind.Null or JsonValueKind.Undefined => "null",
_ => throw new UserFriendlyException("Invalid filter."),
};
2026-02-24 20:44:16 +00:00
var fieldFilter = fieldName.Split('.');
2026-10-05 12:58:59 +00:00
var filterFieldName = fieldFilter.First();
2026-02-24 20:44:16 +00:00
var filterGroupInterval = fieldFilter.Length > 1 ? fieldFilter.Last().ToLower() : null;
var listFormField = listFormFields.FirstOrDefault(c => c.FieldName == filterFieldName);
2026-09-30 20:52:33 +00:00
// Anahtar alan her sorguya eklenir ama alan listesinde olmayabilir; grid satira odaklanirken
// filtreye anahtar kosulu yazar.
var isKeyField = listFormField == null && filterFieldName == KeyFieldName;
if (listFormField == null && !isKeyField)
2026-02-24 20:44:16 +00:00
{
2026-10-05 12:58:59 +00:00
return "1 = 1";
}
var filterOperator = op;
if (!FilterOperators.Contains(filterOperator))
{
throw new UserFriendlyException($"'{op}' is not a supported filter operator.");
2026-02-24 20:44:16 +00:00
}
2026-10-05 12:58:59 +00:00
var filterParamKey = $"@{filterFieldName}{filterParameterIndex++}";
var filterParamKeyLower = filterParamKey;
2026-02-24 20:44:16 +00:00
// filterGroupInterval sayı olduğu için (örn OrderDate.Month), eğer bu gelirse sayıya parse ediyoruz,
// diğer durumlarda field tipine parse ediyoruz
DbType dbType = filterGroupInterval switch
{
"year" or "quarter" or "month" or "day" or "dayOfWeek" => DbType.Int32,
2026-09-30 20:52:33 +00:00
_ => isKeyField ? KeyFieldDbSourceType : listFormField.SourceDbType
2026-02-24 20:44:16 +00:00
};
2026-10-05 12:58:59 +00:00
var filterValue = fieldValue;
2026-02-24 20:44:16 +00:00
if (filterValue == "null")
{
if (filterOperator == "=") filterOperator = "isblank";
if (filterOperator == "<>") filterOperator = "isnotblank";
}
var filterSqlFieldName = ConvertAliasToFieldName(filterFieldName, listFormFields, filterGroupInterval);
if (filterOperator == "=" && DataSourceType == DataSourceTypeEnum.Postgresql && (
dbType == DbType.String ||
dbType == DbType.AnsiString))
{
filterSqlFieldName = $"lower({filterSqlFieldName})";
filterParamKeyLower = $"lower({filterParamKeyLower})";
filterValue = filterValue.Replace('I', 'ı');
}
var sqlOperator = "";
string formatSql = "{0} {1} {2}";
string concatOp =
DataSourceType == DataSourceTypeEnum.Mssql ? "+" :
DataSourceType == DataSourceTypeEnum.Postgresql ? "||"
: "";
string likeOp =
DataSourceType == DataSourceTypeEnum.Mssql ? "LIKE" :
DataSourceType == DataSourceTypeEnum.Postgresql ? "ILIKE"
: "";
if (filterOperator == "=") sqlOperator = "=";
else if (filterOperator == "<>") sqlOperator = "<>";
else if (filterOperator == "<") sqlOperator = "<";
else if (filterOperator == "<=") sqlOperator = "<=";
else if (filterOperator == ">") sqlOperator = ">";
else if (filterOperator == ">=") sqlOperator = ">=";
else if (filterOperator == "contains") formatSql = "{0} " + likeOp + " '%' " + concatOp + " {2} " + concatOp + " '%'";
else if (filterOperator == "notcontains") formatSql = "{0} NOT " + likeOp + " '%' " + concatOp + " {2} " + concatOp + " '%'";
else if (filterOperator == "startswith") formatSql = "{0} " + likeOp + " {2} " + concatOp + " '%'";
else if (filterOperator == "endswith") formatSql = "{0} " + likeOp + " '%' " + concatOp + " {2}";
else if (filterOperator == "isblank") formatSql = "{0} IS NULL";
else if (filterOperator == "isnotblank") formatSql = "{0} IS NOT NULL";
2026-10-05 12:58:59 +00:00
var sqlPart = string.Format(formatSql, filterSqlFieldName, sqlOperator, filterParamKeyLower);
2026-02-24 20:44:16 +00:00
if (formatSql.Contains("{2}"))
{
SelectQueryParameters.Add(filterParamKey, QueryHelper.GetFormattedValue(dbType, filterValue));
}
return sqlPart;
}
private List<string> GetSortFields(ListForm listform,
List<ListFormField> listFormFields,
string sort = null)
{
var sortFields = new List<string>();
if (listform.SortMode == PlatformConsts.GridOptions.SortModeNone)
{
sortFields.Add("CURRENT_TIMESTAMP");
return SortParts;
}
// kullanicinin islemi ile siralama yapiliyor ise
if (!string.IsNullOrEmpty(sort))
{
var sorts = sort.Split(PlatformConsts.MultiValueDelimiter, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < sorts.Length; i++)
{
// sorts[i] su sekilde gelir: "AlanIsmi desc". Metin istemciden gelir ve ORDER BY'a
// yazilir; alan adi tirnak kiramaz, yon yalnizca asc/desc olabilir.
2026-02-24 20:44:16 +00:00
var sortElem = sorts[i].Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (sortElem.Length is 0 or > 2 || sortElem[0].Contains('"'))
{
continue;
}
var direction = sortElem.Length == 2 && sortElem[1].Equals("desc", StringComparison.OrdinalIgnoreCase) ? "desc" : "asc";
sortFields.Add($"\"{sortElem[0]}\" {direction}"); // Anahtar kelime adli alanlar (Key gibi) icin tirnak icinde
2026-02-24 20:44:16 +00:00
}
}
else // ekran tanimi ile siralama yapiliyor ise
{
foreach (var itemField in listFormFields.Where(c => c.SortIndex.HasValue && c.SortIndex > 0).OrderBy(c => c.SortIndex))
{
sortFields.Add($"\"{itemField.FieldName}\" {itemField.SortDirection}");
}
}
// Varsayilan olarak siramalama yok ise siralamayi etkilemeyecek bir alan ekle (paging islemlerinde Order By bolumu istenildigi icin eklendi)
if (sortFields.Count == 0)
{
sortFields.Add("CURRENT_TIMESTAMP");
}
return sortFields;
}
private string GetJoinString()
{
return " " + string.Join(' ', JoinParts);
}
private string GetWhereString()
{
if (WhereParts.Count == 0)
return "";
return " WHERE " + string.Join(' ', WhereParts);
}
private string GetSortString()
{
return " ORDER BY " + string.Join(',', SortParts);
}
private string GetPagingString(int take, int? skip = 0)
{
return $" OFFSET {skip} ROWS FETCH NEXT {take} ROWS ONLY";
}
private string GetTotalCountQuery()
{
2026-09-30 20:52:33 +00:00
var sql = $"SELECT COUNT(\"{TableName}\".\"{KeyFieldName}\") FROM {From} AS \"{TableName}\"";
2026-02-24 20:44:16 +00:00
sql += GetJoinString();
sql += GetWhereString();
return sql;
}
private string GetSummaryQuery(string fieldName, string summaryType)
{
2026-09-30 20:52:33 +00:00
var sql = $"SELECT {summaryType}({fieldName}) FROM {From} AS \"{TableName}\"";
2026-02-24 20:44:16 +00:00
sql += GetJoinString();
sql += GetWhereString();
return sql;
}
private string GetGroupQuery(List<ListFormField> listFormFields, QueryParameters queryParams = null)
{
if (queryParams == null || queryParams.Group.IsNullOrWhiteSpace())
{
return null;
}
var validSummaryTypes = new string[] { "sum", "avg", "min", "max", "count" };
var validSortTypes = new string[] { "asc", "desc" };
var validGroupIntervals = new string[] { "day", "dayOfWeek", "month", "quarter", "year" };
// queryParams.Group veri yapisi:
// AlanIsmi desc isExpanded groupInterval
// Örn: group:IsEnabled asc false|TextDate asc false year|TextDate asc false month|CultureName asc false
// yapilan grup islemlerinde her bir gruplama icin bir ya da birden fazla istek geliyor.
var groups = queryParams.Group?.Split(PlatformConsts.MultiValueDelimiter);
foreach (var (group, i) in groups.Select((a, i) => (a, i)))
{
var items = group.Trim().Split(' ');
if (!items.Length.IsBetween(2, 4))
{
continue;
}
var field = items[0];
var sort = validSortTypes.Contains(items[1]) ? items[1] : "asc";
var isExpanded = items.Length > 2 && items[2] == "true";
var groupInterval = items.Length > 3 ? validGroupIntervals.Contains(items[3]) ? items[3] : null : null;
var fieldName = ConvertAliasToFieldName(field, listFormFields, groupInterval);// Alias li bir alan ise gercek alani bul
2026-10-05 12:58:59 +00:00
if (fieldName == UnknownFieldName)
{
continue;
}
2026-02-24 20:44:16 +00:00
GroupTuples.Add((i, fieldName, $"Group_{i}", sort, isExpanded, groupInterval));
}
2026-10-05 12:58:59 +00:00
if (GroupTuples.Count == 0)
{
return null;
}
2026-02-24 20:44:16 +00:00
// gruplanan bir alan icin grup satirinda summary bilgisinin gorunmesi icin gerekli veri yapisidir,
// grup sorgusu ile birlikte calistirilir.
// Gelen veri yapisi: FieldName SummaryType|FieldName SummaryType
// Örn: groupSummary:TextCount sum|TextCount count
// Not: Gelen sira ile ayni sekilde geri dondurulmelidir! GroupSummaryItems bunun icin kullaniliyor
var groupSummaries = queryParams.GroupSummary?.Split(PlatformConsts.MultiValueDelimiter);
if (!groupSummaries.IsNullOrEmpty())
{
foreach (var (groupSummary, i) in groupSummaries.Select((a, i) => (a, i)))
{
var gs = groupSummary.Trim().Split(' ');
if (gs.Length != 2)
{
continue;
}
var field = gs[0].ToString();
var summaryType = gs[1].ToString();
if (!validSummaryTypes.Contains(summaryType))
{
continue;
}
var fieldName = ConvertAliasToFieldName(field, listFormFields);// Alias li bir alan ise gercek alani bul
2026-10-05 12:58:59 +00:00
if (fieldName == UnknownFieldName)
{
continue;
}
2026-02-24 20:44:16 +00:00
GroupSummaryTuples.Add((i, fieldName, $"Summary_{i}", summaryType));
}
}
var sqlSub = new StringBuilder();
sqlSub.Append("SELECT ");
sqlSub.Append(string.Join(',', GroupTuples.Select((a) => $"{a.Field} AS \"{a.SelectExpr}\"")));
sqlSub.Append(string.Join("", GroupSummaryTuples.Select((a) => $",{a.SummaryType}({a.Field}) AS \"{a.SelectExpr}\"")));
sqlSub.Append(",COUNT(1) AS \"Summary_Count\"");
2026-09-30 20:52:33 +00:00
sqlSub.Append($" FROM {From} AS \"{TableName}\"");
2026-02-24 20:44:16 +00:00
sqlSub.Append(GetJoinString());
sqlSub.Append(GetWhereString());
sqlSub.Append($" GROUP BY {string.Join(',', GroupTuples.Select((a) => a.Field))}");
sqlSub.Append($" ORDER BY {string.Join(',', GroupTuples.Select((a, i) => $"\"{a.SelectExpr}\" {a.Sort}"))}");
if (queryParams.Take > 0)
{
sqlSub.Append(GetPagingString(queryParams.Take, queryParams.Skip));
}
else
{
sqlSub.Append(" OFFSET 0 ROWS");
}
var sqlMain = new StringBuilder();
sqlMain.Append("SELECT ");
sqlMain.Append(string.Join(',', GroupTuples.Select((a) => $"\"{a.SelectExpr}\"")));
foreach (var item in GroupTuples.Select((groupTuple, i) => (groupTuple, i)))
{
var prevItems = GroupTuples.Take(item.i + 1);
if (prevItems.Count() == 1)
{
sqlMain.Append($",\"{item.groupTuple.SelectExpr}\" AS \"{item.groupTuple.SelectExpr}_Key\"");
}
else
{
sqlMain.Append($",CONCAT({string.Join(",'" + PlatformConsts.MultiValueDelimiter + "',", prevItems.Select(a => $"\"{a.SelectExpr}\""))}) AS \"{item.groupTuple.SelectExpr}_Key\"");
}
}
sqlMain.Append(", \"Summary_Count\"");
foreach (var summaryTuple in GroupSummaryTuples)
{
var sType = summaryTuple.SummaryType == "count" ? "sum" : summaryTuple.SummaryType;
var summarySelect = $"{sType}(\"{summaryTuple.SelectExpr}\")";
sqlMain.Append($",{summarySelect} OVER() AS \"{summaryTuple.SelectExpr}_All\"");
var partitionBy = new List<string>();
foreach (var groupTuple in GroupTuples)
{
partitionBy.Add($"\"{groupTuple.SelectExpr}\"");
sqlMain.Append($",{summarySelect} OVER(PARTITION BY {string.Join(',', partitionBy)}) AS \"{summaryTuple.SelectExpr}_{groupTuple.Index}\"");
}
partitionBy.Clear();
}
// foreach (var summaryTuple in GroupSummaryTuples)
// {
// var summarySelect = $"{summaryTuple.SummaryType}({summaryTuple.SelectExpr})";
// sqlMain.Append($",{summarySelect} OVER() AS {summaryTuple.SelectExpr}_All");
// foreach (var item in GroupTuples.Select((tuple, i) => (tuple, i)))
// {
// var groupTuple = item.tuple;
// if (item.i == 0)
// {
// sqlMain.Append($",{summaryTuple.SelectExpr} AS {summaryTuple.SelectExpr}_{groupTuple.Index}");
// }
// else
// {
// sqlMain.Append($",{summarySelect} OVER(PARTITION BY {groupTuple.SelectExpr}) AS {summaryTuple.SelectExpr}_{groupTuple.Index}");
// }
// }
// }
sqlMain.Append($" FROM ({sqlSub}) AS \"Sub\"");
sqlMain.Append($" ORDER BY {string.Join(',', GroupTuples.Select((a, i) => $"\"{a.SelectExpr}\" {a.Sort}"))}");
return sqlMain.ToString();
}
private string GetDeleteQuery(QueryParameters queryParams = null)
{
if (queryParams == null || queryParams.CreateDeleteQuery == false)
return null;
return $"DELETE \"{TableName}\" FROM {SelectCommand} AS \"{TableName}\" {GetWhereString()}";
}
public string GetChartQuery(ListForm listform, QueryParameters queryParams = null)
{
if (listform == null || string.IsNullOrWhiteSpace(listform.SeriesJson) || queryParams == null)
return null;
var seriesList = JsonSerializer.Deserialize<List<ChartSeries>>(listform.SeriesJson);
if (seriesList == null || seriesList.Count == 0)
return null;
// ArgumentField listesi
var argumentFields = seriesList.Select(s => s.ArgumentField).Distinct().ToList();
// ArgumentField ifadesini oluştur
string argumentExpression;
if (argumentFields.Count == 1)
{
// Tek ArgumentField
argumentExpression = $"[{argumentFields.First()}]";
}
else
{
argumentExpression = string.Join(PlatformConsts.MultiValueDelimiter, argumentFields.Select(f => $"[{f}]"));
2026-02-24 20:44:16 +00:00
}
// Select sütunlarını oluştur
var selectParts = new List<string> { $"{argumentExpression} AS ArgumentField" };
foreach (var series in seriesList)
{
string sqlFunc = series.SummaryType.ToUpper() switch
{
"COUNT" => $"COUNT([{series.ValueField}])",
"SUM" => $"SUM([{series.ValueField}])",
"AVG" => $"AVG([{series.ValueField}])",
"MIN" => $"MIN([{series.ValueField}])",
"MAX" => $"MAX([{series.ValueField}])",
_ => throw new NotSupportedException($"Unsupported SummaryType: {series.SummaryType}")
};
selectParts.Add($"{sqlFunc} AS [{series.Name}]");
}
// SQL cümlesi
var sql = $@"
SELECT {string.Join(", ", selectParts)}
FROM [{listform.SelectCommand}]
{GetWhereString()}
GROUP BY {argumentExpression}";
return sql;
}
}