Skip to content

Commit fe35123

Browse files
committed
Add UseOnDuplicateKeyUpdateForSplitTables
1 parent 96052d4 commit fe35123

7 files changed

Lines changed: 135 additions & 30 deletions

File tree

WowPacketParser/App.config

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,15 @@
228228
-->
229229
<add key="ForceInsertQueries" value="false"/>
230230

231+
<!--
232+
Option: UseOnDuplicateKeyUpdateForSplitTables
233+
Description: For tables generated in multiple passes (for example creature_template and creature_template_difficulty),
234+
use INSERT ... ON DUPLICATE KEY UPDATE instead of DELETE + INSERT so one pass does not remove
235+
rows/columns generated by another pass.
236+
Default: "false"
237+
-->
238+
<add key="UseOnDuplicateKeyUpdateForSplitTables" value="false"/>
239+
231240
<!--
232241
Option: MaximumConditionsPerStatement
233242
Description: Defined after how many WHERE conditions a new statement should be created for UPDATE and DELETE FROM

WowPacketParser/Misc/Settings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public static class Settings
2727
public static readonly bool SkipRowsWithFallbackValues = Conf.GetBoolean("SkipRowsWithFallbackValues", true);
2828
public static readonly bool IgnoreZeroValues = Conf.GetBoolean("IgnoreZeroValues", false);
2929
public static readonly bool ForceInsertQueries = Conf.GetBoolean("ForceInsertQueries", false);
30+
public static readonly bool UseOnDuplicateKeyUpdateForSplitTables = Conf.GetBoolean("UseOnDuplicateKeyUpdateForSplitTables", false);
3031
public static readonly int MaximumConditionsPerStatement = Conf.GetInt("MaximumConditionsPerStatement", 5000);
3132
public static readonly bool RecalcDiscount = Conf.GetBoolean("RecalcDiscount", false);
3233
public static readonly bool ForcePhaseZero = Conf.GetBoolean("ForcePhaseZero", false);

WowPacketParser/SQL/Builders/Movement.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ protected override string GenerateQuery()
177177
}
178178
}
179179

180+
output.ReplaceLastCommaWithSemicolon();
180181
output.AppendLine();
181182

182183
++pathIdCounter;

WowPacketParser/SQL/Builders/UnitMisc.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,11 @@ public static string CreatureTemplateScalingDataWDB()
155155

156156
var templatesDb = SQLDatabase.Get(Storage.CreatureTemplateDifficultiesWDB);
157157

158-
return SQLUtil.Compare(Settings.SQLOrderByKey ? Storage.CreatureTemplateDifficultiesWDB.OrderBy(x => x.Item1.Entry).ToArray() : Storage.CreatureTemplateDifficultiesWDB.ToArray(), templatesDb, StoreNameType.Unit);
158+
return SQLUtil.Compare(
159+
Settings.SQLOrderByKey ? Storage.CreatureTemplateDifficultiesWDB.OrderBy(x => x.Item1.Entry).ToArray() : Storage.CreatureTemplateDifficultiesWDB.ToArray(),
160+
templatesDb,
161+
StoreNameType.Unit,
162+
Settings.UseOnDuplicateKeyUpdateForSplitTables);
159163
}
160164

161165
public static void UpdateCreatureStaticFlags(ref Unit npc, ref CreatureTemplateDifficulty creatureDifficulty)
@@ -265,7 +269,8 @@ public static string CreatureTemplateScalingData(Dictionary<WowGuid, Unit> units
265269
sb.Remove(sb.Length - 3, 3);
266270

267271
return $"{StoreGetters.GetName(StoreNameType.Unit, (int)x.Entry)} - {sb}";
268-
});
272+
},
273+
Settings.UseOnDuplicateKeyUpdateForSplitTables);
269274
}
270275

271276
[BuilderMethod(Units = true)]
@@ -955,7 +960,7 @@ public static string CreatureTemplateNonWDB(Dictionary<WowGuid, Unit> units)
955960
}
956961

957962
var templatesDb = SQLDatabase.Get(Storage.CreatureTemplatesNonWDB);
958-
return SQLUtil.Compare(Storage.CreatureTemplatesNonWDB, templatesDb, StoreNameType.Unit);
963+
return SQLUtil.Compare(Storage.CreatureTemplatesNonWDB, templatesDb, StoreNameType.Unit, Settings.UseOnDuplicateKeyUpdateForSplitTables);
959964
}
960965

961966
static UnitMisc()

WowPacketParser/SQL/Builders/WDBTemplates.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,13 @@ public static string CreatureTemplate(Dictionary<WowGuid, Unit> units)
103103
if (!Storage.CreatureTemplates.IsEmpty() && Settings.TargetedDatabase != TargetedDatabase.Classic)
104104
{
105105
var templatesDb = SQLDatabase.Get(Storage.CreatureTemplates.Values);
106-
return SQLUtil.Compare(Storage.CreatureTemplates.Values, templatesDb, StoreNameType.Unit);
106+
return SQLUtil.Compare(Storage.CreatureTemplates.Values, templatesDb, StoreNameType.Unit, Settings.UseOnDuplicateKeyUpdateForSplitTables);
107107
}
108108

109109
if (!Storage.CreatureTemplatesClassic.IsEmpty() && Settings.TargetedDatabase == TargetedDatabase.Classic)
110110
{
111111
var templatesDb = SQLDatabase.Get(Storage.CreatureTemplatesClassic);
112-
return SQLUtil.Compare(Storage.CreatureTemplatesClassic, templatesDb, StoreNameType.Unit);
112+
return SQLUtil.Compare(Storage.CreatureTemplatesClassic, templatesDb, StoreNameType.Unit, Settings.UseOnDuplicateKeyUpdateForSplitTables);
113113
}
114114

115115
return string.Empty;

WowPacketParser/SQL/QueryBuilder.cs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,9 @@ public virtual string Build()
408408
{
409409
private readonly RowList<T> _rows;
410410
private readonly bool _withDelete;
411+
private readonly bool _onDuplicateKeyUpdate;
411412
private readonly string _insertHeader;
413+
private readonly string _onDuplicateKeyUpdateClause;
412414

413415
// Add a new insert header every 250 rows
414416
private const int MaxRowsPerInsert = 250;
@@ -420,11 +422,13 @@ public virtual string Build()
420422
/// <param name="rows">A list of <see cref="SQLInsertRow{T}"/> rows</param>
421423
/// <param name="withDelete">If set to false the full query will not include a delete query</param>
422424
/// <param name="ignore">If set to true the INSERT INTO query will be INSERT IGNORE INTO</param>
423-
public SQLInsert(RowList<T> rows, bool withDelete = true, bool ignore = false)
425+
public SQLInsert(RowList<T> rows, bool withDelete = true, bool ignore = false, bool onDuplicateKeyUpdate = false)
424426
{
425427
_rows = rows;
426428
_insertHeader = new SQLInsertHeader<T>(ignore).Build();
427429
_withDelete = withDelete;
430+
_onDuplicateKeyUpdate = onDuplicateKeyUpdate;
431+
_onDuplicateKeyUpdateClause = BuildOnDuplicateKeyUpdateClause();
428432
}
429433

430434
/// <summary>
@@ -447,7 +451,7 @@ public string Build()
447451
{
448452
if (count >= MaxRowsPerInsert)
449453
{
450-
query.ReplaceLastCommaWithSemicolon();
454+
FinalizeCurrentInsert(query);
451455
query.Append(Environment.NewLine);
452456
query.Append(_insertHeader);
453457
count = 0;
@@ -456,10 +460,49 @@ public string Build()
456460
query.Append(Environment.NewLine);
457461
count++;
458462
}
459-
query.ReplaceLastCommaWithSemicolon();
463+
FinalizeCurrentInsert(query);
460464

461465
return query.ToString();
462466
}
467+
468+
private void FinalizeCurrentInsert(StringBuilder query)
469+
{
470+
if (_onDuplicateKeyUpdate && !string.IsNullOrEmpty(_onDuplicateKeyUpdateClause))
471+
{
472+
query.ReplaceLastComma($" {_onDuplicateKeyUpdateClause};");
473+
return;
474+
}
475+
476+
query.ReplaceLastCommaWithSemicolon();
477+
}
478+
479+
private static string BuildOnDuplicateKeyUpdateClause()
480+
{
481+
var fields = SQLUtil.GetFields<T>();
482+
var assignments = new List<string>();
483+
484+
foreach (var field in fields)
485+
{
486+
var firstAttribute = field.Item3.First();
487+
if (firstAttribute.IsPrimaryKey)
488+
continue;
489+
490+
if (field.Item2.FieldType.IsArray)
491+
{
492+
for (var i = 0; i < firstAttribute.Count; ++i)
493+
{
494+
var fieldName = SQLUtil.AddBackQuotes(firstAttribute.Name + (firstAttribute.StartAtZero ? i : i + 1));
495+
assignments.Add($"{fieldName}=VALUES({fieldName})");
496+
}
497+
498+
continue;
499+
}
500+
501+
assignments.Add($"{field.Item1}=VALUES({field.Item1})");
502+
}
503+
504+
return assignments.Count == 0 ? string.Empty : "ON DUPLICATE KEY UPDATE " + string.Join(SQLUtil.CommaSeparator, assignments);
505+
}
463506
}
464507

465508
internal class SQLInsertHeader<T> : ISQLQuery where T : IDataModel

WowPacketParser/SQL/SQLUtil.cs

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,33 +65,78 @@ public static string EscapeString(string str)
6565
}
6666

6767
/// <summary>
68-
/// Replaces the last comma with semicolon in StringBuilder
68+
/// Replaces the last comma with a custom suffix in StringBuilder.
69+
/// Walks backward until it finds the last non-comment line.
6970
/// </summary>
70-
/// <param name="str"></param>
71-
/// <returns></returns>
72-
public static void ReplaceLastCommaWithSemicolon(this StringBuilder str)
71+
public static void ReplaceLastComma(this StringBuilder str, string replacement)
7372
{
74-
bool isComment = false;
75-
int lastCommaPos = -1;
76-
for (var i = str.Length - 1; i > 0; i--)
73+
for (var lineEnd = str.Length - 1; lineEnd >= 0;)
7774
{
78-
if (i >= 3 && str[i - 3] == ',' && str[i - 2] == ' ' && str[i - 1] == '-' && str[i] == '-')
75+
while (lineEnd >= 0 && (str[lineEnd] == '\r' || str[lineEnd] == '\n'))
76+
--lineEnd;
77+
78+
if (lineEnd < 0)
79+
return;
80+
81+
var lineStart = lineEnd;
82+
while (lineStart > 0 && str[lineStart - 1] != '\r' && str[lineStart - 1] != '\n')
83+
--lineStart;
84+
85+
var contentStart = lineStart;
86+
while (contentStart <= lineEnd && char.IsWhiteSpace(str[contentStart]))
87+
++contentStart;
88+
89+
if (contentStart > lineEnd)
7990
{
80-
str[i - 3] = ';';
81-
isComment = true;
82-
break;
91+
lineEnd = lineStart - 1;
92+
continue;
93+
}
94+
95+
if (contentStart + 1 <= lineEnd && str[contentStart] == '-' && str[contentStart + 1] == '-')
96+
{
97+
lineEnd = lineStart - 1;
98+
continue;
99+
}
100+
101+
var commentStart = -1;
102+
for (var i = contentStart; i < lineEnd; ++i)
103+
{
104+
if (str[i] == '-' && str[i + 1] == '-')
105+
{
106+
commentStart = i;
107+
break;
108+
}
83109
}
84110

85-
if (lastCommaPos == -1 && str[i] == ',')
86-
lastCommaPos = i;
111+
var contentEnd = commentStart == -1 ? lineEnd : commentStart - 1;
112+
while (contentEnd >= contentStart && char.IsWhiteSpace(str[contentEnd]))
113+
--contentEnd;
114+
115+
for (var i = contentEnd; i >= contentStart; --i)
116+
{
117+
if (str[i] == ';')
118+
return;
119+
120+
if (str[i] == ',')
121+
{
122+
str.Remove(i, 1);
123+
str.Insert(i, replacement);
124+
return;
125+
}
126+
}
87127

88-
// only interact with last line, skip trailing newline
89-
if ((str[i] == '\n' || str[i] == '\r') && i < str.Length - 3)
90-
break;
128+
lineEnd = lineStart - 1;
91129
}
130+
}
92131

93-
if (!isComment && lastCommaPos != -1)
94-
str[lastCommaPos] = ';';
132+
/// <summary>
133+
/// Replaces the last comma with semicolon in StringBuilder
134+
/// </summary>
135+
/// <param name="str"></param>
136+
/// <returns></returns>
137+
public static void ReplaceLastCommaWithSemicolon(this StringBuilder str)
138+
{
139+
str.ReplaceLastComma(";");
95140
}
96141

97142
/// <summary>
@@ -208,14 +253,15 @@ public static bool IsHotfixTable<T>() where T : IDataModel
208253
/// <param name="storeList"><see cref="DataBag{T}"/> with items form sniff.</param>
209254
/// <param name="dbList"><see cref="DataBag{T}"/> with items from database.</param>
210255
/// <param name="storeType">Are we dealing with Spells, Quests, Units, ...?</param>
211-
public static string Compare<T>(IEnumerable<Tuple<T, TimeSpan?>> storeList, RowList<T> dbList, StoreNameType storeType)
256+
public static string Compare<T>(IEnumerable<Tuple<T, TimeSpan?>> storeList, RowList<T> dbList, StoreNameType storeType, bool useOnDuplicateKeyUpdateForInserts = false)
212257
where T : IDataModel, new()
213258
{
214259
var primaryKey = GetFirstPrimaryKey<T>();
215260
return Compare(storeList, dbList,
216261
t => storeType != StoreNameType.None
217262
? StoreGetters.GetName(storeType, Convert.ToInt32(primaryKey.GetValue(t)), false)
218-
: "");
263+
: "",
264+
useOnDuplicateKeyUpdateForInserts);
219265
}
220266

221267
/// <summary>
@@ -229,7 +275,7 @@ public static string Compare<T>(IEnumerable<Tuple<T, TimeSpan?>> storeList, RowL
229275
/// <param name="dbList">Dictionary retrieved from DB</param>
230276
/// <param name="commentSetter"></param>
231277
/// <returns>A string containing full SQL queries</returns>
232-
public static string Compare<T>(IEnumerable<Tuple<T, TimeSpan?>> storeList, RowList<T> dbList, Func<T, string> commentSetter)
278+
public static string Compare<T>(IEnumerable<Tuple<T, TimeSpan?>> storeList, RowList<T> dbList, Func<T, string> commentSetter, bool useOnDuplicateKeyUpdateForInserts = false)
233279
where T : IDataModel, new()
234280
{
235281
if (!IsTableVisible<T>())
@@ -334,7 +380,7 @@ public static string Compare<T>(IEnumerable<Tuple<T, TimeSpan?>> storeList, RowL
334380
}
335381
}
336382

337-
return new SQLInsert<T>(rowsIns).Build() + Environment.NewLine +
383+
return new SQLInsert<T>(rowsIns, withDelete: !useOnDuplicateKeyUpdateForInserts, onDuplicateKeyUpdate: useOnDuplicateKeyUpdateForInserts).Build() + Environment.NewLine +
338384
new SQLUpdate<T>(rowsUpd).Build();
339385
}
340386

0 commit comments

Comments
 (0)