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