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 ;
2026-08-21 19:27:59 +00:00
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
{
2026-05-23 21:12:01 +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
}
2026-05-23 21:12:01 +00:00
private void ResetQueryState ( )
{
SelectCommand = null ;
TableName = null ;
KeyFieldName = null ;
2026-09-30 20:52:33 +00:00
KeyFieldDbSourceType = default ;
2026-05-23 21:12:01 +00:00
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
{
2026-03-10 21:18:35 +00:00
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}'" ) ;
}
}
2026-06-07 16:26:19 +00:00
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 )
2026-06-07 16:26:19 +00:00
{
if ( whereParts . Any ( ) )
{
whereParts . Add ( "AND" ) ;
}
2026-06-07 19:42:02 +00:00
// 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.
2026-07-08 07:29:45 +00:00
whereParts . Add ( GetWorkflowApproverFilter ( workflow . ApprovalUserFieldName ) ) ;
2026-06-07 16:26:19 +00:00
}
}
2026-02-24 20:44:16 +00:00
if ( ! whereParts . Any ( ) )
{
whereParts . Add ( "1 = 1" ) ;
}
return whereParts ;
}
2026-07-08 07:29:45 +00:00
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 + + )
{
2026-09-24 11:58:23 +00:00
// 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 ) ;
2026-09-24 11:58:23 +00:00
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
{
2026-07-08 07:29:45 +00:00
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 ;
}
}