Skip to content

Commit b6427a3

Browse files
committed
[SQL] Query parser can parse more queries
1 parent b9e4063 commit b6427a3

33 files changed

+742
-274
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
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

Comments
 (0)