1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Diagnostics ;
4+ using System . Linq ;
5+ using System . Threading . Tasks ;
6+ using WDE . Common . Services ;
7+ using WDE . Common . Services . QueryParser ;
8+ using WDE . Common . Services . QueryParser . Models ;
9+ using WDE . Common . Sessions ;
10+ using WDE . Common . Utils ;
11+ using WDE . DatabaseEditors . Data . Interfaces ;
12+ using WDE . DatabaseEditors . Data . Structs ;
13+ using WDE . DatabaseEditors . Loaders ;
14+ using WDE . DatabaseEditors . Solution ;
15+ using WDE . Module . Attributes ;
16+
17+ namespace WDE . DatabaseEditors . Services ;
18+
19+ [ AutoRegister ]
20+ public class GenericTableQueryParserProvider : IQueryParserProvider
21+ {
22+ private readonly ITableDefinitionProvider tableDefinitionProvider ;
23+ private readonly IDatabaseTableDataProvider loader ;
24+ private readonly ISessionService sessionService ;
25+
26+ private Dictionary < string , DatabaseTableSolutionItem > byTable = new ( ) ;
27+ private Dictionary < string , List < DatabaseKey > > byTableToDelete = new ( ) ;
28+
29+ public GenericTableQueryParserProvider ( ITableDefinitionProvider tableDefinitionProvider ,
30+ IDatabaseTableDataProvider loader ,
31+ ISessionService sessionService )
32+ {
33+ this . tableDefinitionProvider = tableDefinitionProvider ;
34+ this . loader = loader ;
35+ this . sessionService = sessionService ;
36+ }
37+
38+ private async Task < IList < DatabaseKey > > TryEvaluateKeys ( DatabaseTableDefinitionJson definition ,
39+ DatabaseForeignTableJson ? foreign ,
40+ EqualityWhereCondition condition ,
41+ IQueryParsingContext context )
42+ {
43+ List < DatabaseKey > keys = new ( ) ;
44+ if ( TryBuildKey ( definition , foreign , condition , out var key ) )
45+ {
46+ keys . Add ( key ) ;
47+ return keys ;
48+ }
49+ else if ( definition . RecordMode == RecordMode . SingleRow )
50+ {
51+ var result = await loader . Load ( definition . Id , condition . RawSql , 0 , 100 , null ) ;
52+ if ( result == null )
53+ return keys ;
54+ if ( result . Entities . Count == 100 )
55+ {
56+ context . AddError ( $ "Finding keys for table { definition . TableName } with condition '" + condition . RawSql + "' returned more than 100 rows, skpping" ) ;
57+ return keys ;
58+ }
59+ keys . AddRange ( result . Entities . Select ( e => e . Key ) ) ;
60+ return keys ;
61+ }
62+ return keys ;
63+ }
64+
65+ private bool TryBuildKey ( DatabaseTableDefinitionJson definition , DatabaseForeignTableJson ? foreign , EqualityWhereCondition condition , out DatabaseKey key )
66+ {
67+ key = default ;
68+ if ( condition . Columns . Length != definition . GroupByKeys . Count ||
69+ definition . GroupByKeys . Count == 0 )
70+ return false ;
71+
72+ List < long > ? values = null ;
73+ foreach ( var groupBy in ( foreign ? . ForeignKeys ?? definition . GroupByKeys ) )
74+ {
75+ int index = condition . Columns . IndexIf ( x => x . Equals ( groupBy , StringComparison . InvariantCultureIgnoreCase ) ) ;
76+ if ( index == - 1 )
77+ return false ;
78+ var value = condition . Values [ index ] ;
79+ if ( value is not long l )
80+ return false ;
81+ if ( definition . GroupByKeys . Count == 1 )
82+ {
83+ key = new DatabaseKey ( l ) ;
84+ return true ;
85+ }
86+ values = values ?? new ( ) ;
87+ values . Add ( l ) ;
88+ }
89+
90+ key = new DatabaseKey ( values ! ) ;
91+ return true ;
92+ }
93+
94+ public async Task < bool > ParseDelete ( DeleteQuery deleteQuery , IQueryParsingContext context )
95+ {
96+ var defi = tableDefinitionProvider . GetDefinitionByTableName ( deleteQuery . TableName ) ;
97+ if ( defi == null )
98+ return false ;
99+
100+ if ( defi . RecordMode == RecordMode . SingleRow )
101+ {
102+ foreach ( var condition in deleteQuery . Where . Conditions )
103+ {
104+ var keys = await TryEvaluateKeys ( defi , null , condition , context ) ;
105+ foreach ( var key in keys )
106+ {
107+ if ( key . Count != defi . GroupByKeys . Count )
108+ continue ;
109+ var old = await loader . Load ( defi . Id , null , null , null , new [ ] { key } ) ;
110+ if ( old != null && old . Entities . Count > 0 )
111+ {
112+ if ( ! byTableToDelete . TryGetValue ( defi . Id , out var toDelete ) )
113+ toDelete = byTableToDelete [ defi . Id ] = new ( ) ;
114+
115+ toDelete . Add ( key ) ;
116+ }
117+ }
118+ }
119+ }
120+ else if ( defi . RecordMode == RecordMode . MultiRecord )
121+ {
122+ foreach ( var condition in deleteQuery . Where . Conditions )
123+ {
124+ if ( ! TryBuildKey ( defi , null , condition , out var key ) )
125+ continue ;
126+ if ( key . Count != defi . GroupByKeys . Count )
127+ continue ;
128+ var item = new DatabaseTableSolutionItem ( defi . Id , defi . IgnoreEquality ) ;
129+ item . Entries . Add ( new SolutionItemDatabaseEntity ( key , false ) ) ;
130+ context . ProduceItem ( item ) ;
131+ }
132+ }
133+
134+ return true ;
135+ }
136+
137+ public async Task < bool > ParseInsert ( InsertQuery query , IQueryParsingContext context )
138+ {
139+ var defi = tableDefinitionProvider . GetDefinitionByTableName ( query . TableName ) ;
140+ if ( defi == null )
141+ return false ;
142+
143+ var keyIndices = defi . GroupByKeys . Select ( key => query . Columns . IndexOf ( key ) ) . ToList ( ) ;
144+ if ( keyIndices . Count != defi . GroupByKeys . Count )
145+ return false ;
146+
147+ if ( defi . RecordMode == RecordMode . SingleRow )
148+ {
149+ DatabaseTableSolutionItem ? existing = null ;
150+ if ( ! byTable . TryGetValue ( defi . Id , out existing ) )
151+ {
152+ var phantom = new DatabaseTableSolutionItem ( defi . Id , defi . IgnoreEquality ) ;
153+ existing = ( sessionService . CurrentSession ? . Find ( phantom ) as DatabaseTableSolutionItem ) ?? phantom ;
154+ context . ProduceItem ( existing ) ;
155+ byTable [ defi . Id ] = existing ;
156+ }
157+ foreach ( var line in query . Inserts )
158+ {
159+ var key = new DatabaseKey ( keyIndices . Select ( index => line [ index ] ) . Select ( x => ( long ) x ) ) ;
160+ if ( existing . Entries . All ( x => x . Key != key ) )
161+ existing . Entries . Add ( new SolutionItemDatabaseEntity ( key , false ) ) ;
162+ if ( byTableToDelete . TryGetValue ( defi . Id , out var toDelete ) )
163+ toDelete . Remove ( key ) ;
164+ }
165+ }
166+ else
167+ {
168+ Debug . Assert ( keyIndices . Count == 1 ) ;
169+ foreach ( var line in query . Inserts )
170+ {
171+ if ( line [ keyIndices [ 0 ] ] is not long lkey )
172+ continue ;
173+ var item = new DatabaseTableSolutionItem ( defi . Id , defi . IgnoreEquality ) ;
174+ item . Entries . Add ( new SolutionItemDatabaseEntity ( new DatabaseKey ( lkey ) , false ) ) ;
175+ context . ProduceItem ( item ) ;
176+ }
177+ }
178+
179+ return true ;
180+ }
181+
182+ public async Task < bool > ParseUpdate ( UpdateQuery query , IQueryParsingContext context )
183+ {
184+ var defi = tableDefinitionProvider . GetDefinitionByTableName ( query . TableName ) ;
185+ DatabaseForeignTableJson ? foreignTable = null ;
186+ if ( defi == null )
187+ {
188+ defi = tableDefinitionProvider . GetDefinitionByForeignTableName ( query . TableName ) ;
189+ if ( defi ? . ForeignTableByName ? . TryGetValue ( query . TableName , out var foreignTable_ ) ?? false )
190+ foreignTable = foreignTable_ ;
191+ }
192+
193+ if ( defi == null )
194+ return false ;
195+
196+ foreach ( var condition in query . Where . Conditions )
197+ {
198+ var keys = await TryEvaluateKeys ( defi , null , condition , context ) ;
199+ foreach ( var key in keys )
200+ {
201+ var old = await loader . Load ( defi . Id , null , null , null , new [ ] { key } ) ;
202+ if ( old == null || old . Entities . Count != 1 || ! old . Entities [ 0 ] . ExistInDatabase )
203+ {
204+ context . AddError ( $ "{ defi . TableName } where { defi . TablePrimaryKeyColumnName } = { key } not found, no update") ;
205+ continue ;
206+ }
207+
208+ List < EntityOrigianlField > originals ;
209+ if ( defi . RecordMode == RecordMode . SingleRow )
210+ {
211+ DatabaseTableSolutionItem ? existing = null ;
212+ if ( ! byTable . TryGetValue ( defi . Id , out existing ) )
213+ {
214+ var phantom = new DatabaseTableSolutionItem ( defi . Id , defi . IgnoreEquality ) ;
215+ existing = ( sessionService . CurrentSession ? . Find ( phantom ) as DatabaseTableSolutionItem ) ?? phantom ;
216+ context . ProduceItem ( existing ) ;
217+ byTable [ defi . Id ] = existing ;
218+ }
219+
220+ var entry = existing . Entries . FirstOrDefault ( e => e . Key == key ) ;
221+ if ( entry == null )
222+ {
223+ entry = new ( key , true ) ;
224+ existing . Entries . Add ( entry ) ;
225+ }
226+ originals = entry . OriginalValues ??= new ( ) ;
227+ }
228+ else
229+ {
230+ var item = new DatabaseTableSolutionItem ( defi . Id , defi . IgnoreEquality ) ;
231+ item . Entries . Add ( new SolutionItemDatabaseEntity ( key , true ) ) ;
232+ var savedItem = sessionService . Find ( item ) ;
233+ if ( savedItem != null )
234+ item = ( DatabaseTableSolutionItem ) savedItem . Clone ( ) ;
235+
236+ originals = item . Entries [ 0 ] . OriginalValues ??= new ( ) ;
237+ context . ProduceItem ( item ) ;
238+ }
239+ foreach ( var upd in query . Updates )
240+ {
241+ var cell = old . Entities [ 0 ] . GetCell ( upd . ColumnName ) ;
242+ if ( cell == null )
243+ continue ;
244+ if ( originals . Any ( o => o . ColumnName == upd . ColumnName ) )
245+ continue ;
246+ var original = new EntityOrigianlField ( )
247+ {
248+ ColumnName = upd . ColumnName ,
249+ OriginalValue = cell . OriginalValue
250+ } ;
251+ originals . Add ( original ) ;
252+ }
253+ }
254+ }
255+
256+ return true ;
257+ }
258+
259+ public void Finish ( IQueryParsingContext context )
260+ {
261+ foreach ( var toDelete in byTableToDelete )
262+ {
263+ DatabaseTableSolutionItem ? existing = null ;
264+ if ( ! byTable . TryGetValue ( toDelete . Key , out existing ) )
265+ {
266+ var phantom = new DatabaseTableSolutionItem ( toDelete . Key , false ) ; // false, because we only have SingleRow here
267+ existing = ( sessionService . CurrentSession ? . Find ( phantom ) as DatabaseTableSolutionItem ) ?? phantom ;
268+ context . ProduceItem ( existing ) ;
269+ }
270+ existing . Entries . RemoveIf ( e => toDelete . Value . Contains ( e . Key ) ) ;
271+ existing . DeletedEntries ??= new ( ) ;
272+ existing . DeletedEntries . AddRange ( toDelete . Value ) ;
273+ existing . DeletedEntries = existing . DeletedEntries . Distinct ( ) . ToList ( ) ;
274+ }
275+ }
276+ }
0 commit comments