using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using SyncEngine.Configuration;
namespace SyncEngine.SqlServer
{
internal enum UniqueKeyCollisionAction
{
FailDuplicate,
StaleUpdateOccupying,
Remap,
Recycle
}
///
/// Pure classification for unique secondary-key collisions (e.g. tblProduct.strCode).
/// No database I/O — extra SQL/Access traffic happens only in UniqueKeyCollisionResolver after error 2601.
///
internal static class UniqueKeyCollisionClassifier
{
public const double SimilarityThreshold = 0.90;
public const string RemovedSuffix = "_removed";
private static readonly Regex CollapseWhitespace = new Regex(@"\s+", RegexOptions.Compiled);
public static UniqueKeyCollisionAction Classify(
bool occupyingPkExistsInAccess,
string occupyingAccessStrCode,
string incomingStrCode,
string occupyingSqlDescription,
string incomingDescription)
{
if (occupyingPkExistsInAccess)
{
if (UniqueValuesEqual(occupyingAccessStrCode, incomingStrCode))
{
return UniqueKeyCollisionAction.FailDuplicate;
}
return UniqueKeyCollisionAction.StaleUpdateOccupying;
}
if (IsSameProduct(occupyingSqlDescription, incomingDescription))
{
return UniqueKeyCollisionAction.Remap;
}
return UniqueKeyCollisionAction.Recycle;
}
public static bool IsUniqueIndexViolation(int sqlErrorNumber)
{
return sqlErrorNumber == 2601 || sqlErrorNumber == 2627;
}
public static string BuildRemovedCode(string original, int maxLength, string occupyingPk)
{
var code = original ?? string.Empty;
if (maxLength <= 0)
{
return string.Empty;
}
if (code.Length + RemovedSuffix.Length <= maxLength)
{
return code + RemovedSuffix;
}
var keep = maxLength - RemovedSuffix.Length;
if (keep > 0)
{
return code.Substring(0, Math.Min(keep, code.Length)) + RemovedSuffix;
}
return BuildPkFallback(maxLength, occupyingPk);
}
public static string BuildPkFallback(int maxLength, string occupyingPk)
{
var suffix = "_r" + (occupyingPk ?? string.Empty);
if (suffix.Length <= maxLength)
{
return suffix;
}
return suffix.Substring(0, maxLength);
}
public static string BuildRefColumnName(string primaryKeyColumn)
{
return "ref" + (primaryKeyColumn ?? string.Empty);
}
public static IList UniqueNonPrimaryIndexes(IEnumerable indexes, string tableName)
{
var result = new List();
if (indexes == null || string.IsNullOrWhiteSpace(tableName))
{
return result;
}
foreach (var index in indexes)
{
if (index == null ||
!string.Equals(index.TableName, tableName, StringComparison.OrdinalIgnoreCase) ||
index.IsPrimary ||
!index.IsUnique ||
index.Columns == null ||
index.Columns.Count != 1)
{
continue;
}
result.Add(index);
}
return result;
}
public static string NormalizeDescription(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
return CollapseWhitespace.Replace(value.Trim(), " ").ToUpperInvariant();
}
internal static bool UniqueValuesEqual(string left, string right)
{
return string.Equals(
(left ?? string.Empty).Trim(),
(right ?? string.Empty).Trim(),
StringComparison.OrdinalIgnoreCase);
}
internal static bool IsSameProduct(string occupyingSqlDescription, string incomingDescription)
{
var left = NormalizeDescription(occupyingSqlDescription);
var right = NormalizeDescription(incomingDescription);
if (left.Length == 0 || right.Length == 0)
{
return true;
}
if (string.Equals(left, right, StringComparison.Ordinal))
{
return true;
}
return Similarity(left, right) >= SimilarityThreshold;
}
internal static double Similarity(string left, string right)
{
if (left == null)
{
left = string.Empty;
}
if (right == null)
{
right = string.Empty;
}
var max = Math.Max(left.Length, right.Length);
if (max == 0)
{
return 1.0;
}
return 1.0 - ((double)Levenshtein(left, right) / max);
}
private static int Levenshtein(string left, string right)
{
var n = left.Length;
var m = right.Length;
var prev = new int[m + 1];
var curr = new int[m + 1];
for (var j = 0; j <= m; j++)
{
prev[j] = j;
}
for (var i = 1; i <= n; i++)
{
curr[0] = i;
for (var j = 1; j <= m; j++)
{
var cost = left[i - 1] == right[j - 1] ? 0 : 1;
curr[j] = Math.Min(
Math.Min(curr[j - 1] + 1, prev[j] + 1),
prev[j - 1] + cost);
}
var swap = prev;
prev = curr;
curr = swap;
}
return prev[m];
}
}
}