in code/Server/CTStore/RedisCache.cs [476:967]
private RedisLuaScript BuildLuaScript(List<Operation> operations)
{
RedisLuaScript script = new RedisLuaScript();
script.Script = string.Empty;
script.Conditions = new List<string>();
script.Actions = new List<string>();
script.Keys = new List<RedisKey>();
script.Values = new List<RedisValue>();
script.ETags = new List<string>();
script.Exceptions = new Dictionary<int, List<OperationFailedException>>();
Dictionary<string, Table> feedTrims = new Dictionary<string, Table>();
int keyIndex = 1;
int valueIndex = 1;
int errorCode = -1;
int resultIndex = 2;
foreach (Operation operation in operations)
{
string key = this.GetKey(operation);
string condition = null;
List<RedisValue> conditionValues = new List<RedisValue>();
bool checkConditionValue = false;
string action = null;
List<RedisValue> actionValues = new List<RedisValue>();
List<OperationFailedException> exceptions = new List<OperationFailedException>();
string resultETag = null;
if (operation.Table is ObjectTable)
{
switch (operation.OperationType)
{
case OperationType.Insert:
condition = string.Format(RedisLuaCommands.ObjectInsertCondition, keyIndex, errorCode);
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.ObjectInsertAction, resultIndex, keyIndex, script.Values.Count + 1, script.Values.Count + actionValues.Count);
exceptions.Add(new ConflictException(Strings.Conflict, -errorCode - 1, null));
resultETag = operation.Entity.CustomETag;
break;
case OperationType.Delete:
if (operation.Entity == null || operation.Entity.ETag == "*")
{
condition = string.Format(RedisLuaCommands.ObjectDeleteCondition, keyIndex, errorCode);
action = string.Format(RedisLuaCommands.ObjectDeleteAction, resultIndex, keyIndex);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
}
else
{
condition = string.Format(RedisLuaCommands.ObjectETagCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(operation.Entity.ETag);
checkConditionValue = true;
action = string.Format(RedisLuaCommands.ObjectDeleteAction, resultIndex, keyIndex);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
exceptions.Add(new PreconditionFailedException(Strings.PreconditionFailed, -errorCode - 1, null));
}
break;
case OperationType.DeleteIfExists:
action = string.Format(RedisLuaCommands.ObjectDeleteAction, resultIndex, keyIndex);
break;
case OperationType.Replace:
if (operation.Entity.ETag == "*")
{
condition = string.Format(RedisLuaCommands.ObjectWilcardReplaceCondition, keyIndex, errorCode);
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.ObjectInsertOrReplaceAction, resultIndex, keyIndex, script.Values.Count + 1, script.Values.Count + actionValues.Count);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
resultETag = operation.Entity.CustomETag;
}
else
{
condition = string.Format(RedisLuaCommands.ObjectETagCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(operation.Entity.ETag);
checkConditionValue = true;
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.ObjectReplaceAction, resultIndex, keyIndex, script.Values.Count + 2, script.Values.Count + actionValues.Count + 1);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
exceptions.Add(new PreconditionFailedException(Strings.PreconditionFailed, -errorCode - 1, null));
resultETag = operation.Entity.CustomETag;
}
break;
case OperationType.InsertOrReplace:
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.ObjectInsertOrReplaceAction, resultIndex, keyIndex, script.Values.Count + 1, script.Values.Count + actionValues.Count);
resultETag = operation.Entity.CustomETag;
break;
case OperationType.Merge:
if (operation.Entity.ETag == "*")
{
condition = string.Format(RedisLuaCommands.ObjectWilcardMergeCondition, keyIndex, errorCode);
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.ObjectInsertOrMergeAction, resultIndex, keyIndex, script.Values.Count + 1, script.Values.Count + actionValues.Count);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
resultETag = operation.Entity.CustomETag;
}
else
{
condition = string.Format(RedisLuaCommands.ObjectETagCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(operation.Entity.ETag);
checkConditionValue = true;
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.ObjectMergeAction, resultIndex, keyIndex, script.Values.Count + 2, script.Values.Count + actionValues.Count + 1);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
exceptions.Add(new PreconditionFailedException(Strings.PreconditionFailed, -errorCode - 1, null));
resultETag = operation.Entity.CustomETag;
}
break;
case OperationType.InsertOrMerge:
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.ObjectInsertOrMergeAction, resultIndex, keyIndex, script.Values.Count + 1, script.Values.Count + actionValues.Count);
resultETag = operation.Entity.CustomETag;
break;
}
}
else if (operation.Table is FixedObjectTable)
{
switch (operation.OperationType)
{
case OperationType.Insert:
condition = string.Format(RedisLuaCommands.FixedObjectInsertCondition, keyIndex, errorCode);
action = string.Format(RedisLuaCommands.FixedObjectInsertAction, resultIndex, keyIndex, valueIndex);
actionValues.Add(this.GetValueBytes(operation));
exceptions.Add(new ConflictException(Strings.Conflict, -errorCode - 1, null));
resultETag = this.ConvertValueToETag(actionValues.Last());
break;
case OperationType.Delete:
if (operation.Entity == null || operation.Entity.ETag == "*")
{
condition = string.Format(RedisLuaCommands.FixedObjectDeleteCondition, keyIndex, errorCode);
action = string.Format(RedisLuaCommands.FixedObjectDeleteAction, resultIndex, keyIndex);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
}
else
{
condition = string.Format(RedisLuaCommands.FixedObjectETagCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(this.ConvertETagToValue(operation.Entity.ETag));
checkConditionValue = true;
action = string.Format(RedisLuaCommands.FixedObjectDeleteAction, resultIndex, keyIndex);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
exceptions.Add(new PreconditionFailedException(Strings.PreconditionFailed, -errorCode - 1, null));
}
break;
case OperationType.DeleteIfExists:
action = string.Format(RedisLuaCommands.FixedObjectDeleteAction, resultIndex, keyIndex);
break;
case OperationType.Replace:
if (operation.Entity.ETag == "*")
{
condition = string.Format(RedisLuaCommands.FixedObjectWilcardReplaceCondition, keyIndex, errorCode);
action = string.Format(RedisLuaCommands.FixedObjectInsertOrReplaceAction, resultIndex, keyIndex, valueIndex);
actionValues.Add(this.GetValueBytes(operation));
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
resultETag = this.ConvertValueToETag(actionValues.Last());
}
else
{
condition = string.Format(RedisLuaCommands.FixedObjectETagCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(this.ConvertETagToValue(operation.Entity.ETag));
checkConditionValue = true;
action = string.Format(RedisLuaCommands.FixedObjectReplaceAction, resultIndex, keyIndex, valueIndex + 1);
actionValues.Add(this.GetValueBytes(operation));
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
exceptions.Add(new PreconditionFailedException(Strings.PreconditionFailed, -errorCode - 1, null));
resultETag = this.ConvertValueToETag(actionValues.Last());
}
break;
case OperationType.InsertOrReplace:
action = string.Format(RedisLuaCommands.FixedObjectInsertOrReplaceAction, resultIndex, keyIndex, valueIndex);
actionValues.Add(this.GetValueBytes(operation));
resultETag = this.ConvertValueToETag(actionValues.Last());
break;
}
}
else if (operation.Table is FeedTable)
{
bool trimCheck = false;
switch (operation.OperationType)
{
case OperationType.Insert:
condition = string.Format(RedisLuaCommands.FeedInsertCondition, keyIndex, valueIndex, valueIndex + 1, errorCode);
conditionValues.Add(this.GetRangeMinItemKey(operation.ItemKey));
conditionValues.Add(this.GetRangeMaxItemKey(operation.ItemKey));
action = string.Format(RedisLuaCommands.FeedInsertAction, resultIndex, keyIndex, valueIndex + 2);
actionValues.Add(this.GetValueBytes(operation));
exceptions.Add(new ConflictException(Strings.Conflict, -errorCode - 1, null));
resultETag = this.ConvertValueToETag(actionValues.Last());
trimCheck = true;
break;
case OperationType.Delete:
if (operation.Entity == null || operation.Entity.ETag == "*")
{
condition = string.Format(RedisLuaCommands.FeedDeleteCondition, keyIndex, valueIndex, valueIndex + 1, errorCode);
conditionValues.Add(this.GetRangeMinItemKey(operation.ItemKey));
conditionValues.Add(this.GetRangeMaxItemKey(operation.ItemKey));
action = string.Format(RedisLuaCommands.FeedDeleteAction, resultIndex, keyIndex, valueIndex, valueIndex + 1);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
}
else
{
condition = string.Format(RedisLuaCommands.FeedETagCondition, keyIndex, valueIndex, valueIndex + 1, valueIndex + 2, errorCode);
conditionValues.Add(this.GetRangeMinItemKey(operation.ItemKey));
conditionValues.Add(this.GetRangeMaxItemKey(operation.ItemKey));
conditionValues.Add(this.ConvertETagToValue(operation.Entity.ETag));
checkConditionValue = true;
action = string.Format(RedisLuaCommands.FeedDeleteAction, resultIndex, keyIndex, valueIndex, valueIndex + 1);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
exceptions.Add(new PreconditionFailedException(Strings.PreconditionFailed, -errorCode - 1, null));
}
break;
case OperationType.DeleteIfExists:
action = string.Format(RedisLuaCommands.FeedDeleteAction, resultIndex, keyIndex, valueIndex, valueIndex + 1);
actionValues.Add(this.GetRangeMinItemKey(operation.ItemKey));
actionValues.Add(this.GetRangeMaxItemKey(operation.ItemKey));
break;
case OperationType.Replace:
if (operation.Entity.ETag == "*")
{
condition = string.Format(RedisLuaCommands.FeedWildcardReplaceCondition, keyIndex, valueIndex, valueIndex + 1, errorCode);
conditionValues.Add(this.GetRangeMinItemKey(operation.ItemKey));
conditionValues.Add(this.GetRangeMaxItemKey(operation.ItemKey));
action = string.Format(RedisLuaCommands.FeedReplaceAction, resultIndex, keyIndex, valueIndex, valueIndex + 1, valueIndex + 2);
actionValues.Add(this.GetValueBytes(operation));
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
resultETag = this.ConvertValueToETag(actionValues.Last());
}
else
{
condition = string.Format(RedisLuaCommands.FeedETagCondition, keyIndex, valueIndex, valueIndex + 1, valueIndex + 2, errorCode);
conditionValues.Add(this.GetRangeMinItemKey(operation.ItemKey));
conditionValues.Add(this.GetRangeMaxItemKey(operation.ItemKey));
conditionValues.Add(this.ConvertETagToValue(operation.Entity.ETag));
checkConditionValue = true;
action = string.Format(RedisLuaCommands.FeedReplaceAction, resultIndex, keyIndex, valueIndex, valueIndex + 1, valueIndex + 3);
actionValues.Add(this.GetValueBytes(operation));
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
exceptions.Add(new PreconditionFailedException(Strings.PreconditionFailed, -errorCode - 1, null));
resultETag = this.ConvertValueToETag(actionValues.Last());
}
break;
case OperationType.InsertOrReplace:
action = string.Format(RedisLuaCommands.FeedInsertOrReplaceAction, resultIndex, keyIndex, valueIndex, valueIndex + 1, valueIndex + 2);
actionValues.Add(this.GetRangeMinItemKey(operation.ItemKey));
actionValues.Add(this.GetRangeMaxItemKey(operation.ItemKey));
actionValues.Add(this.GetValueBytes(operation));
resultETag = this.ConvertValueToETag(actionValues.Last());
trimCheck = true;
break;
case OperationType.InsertOrReplaceIfNotLast:
action = string.Format(RedisLuaCommands.FeedInsertOrReplaceIfNotLastAction, resultIndex, keyIndex, valueIndex, valueIndex + 1, valueIndex + 2);
actionValues.Add(this.GetRangeMinItemKey(operation.ItemKey));
actionValues.Add(this.GetRangeMaxItemKey(operation.ItemKey));
actionValues.Add(this.GetValueBytes(operation));
resultETag = this.ConvertValueToETag(actionValues.Last());
trimCheck = true;
break;
case OperationType.InsertIfNotEmpty:
condition = string.Format(RedisLuaCommands.FeedInsertCondition, keyIndex, valueIndex, valueIndex + 1, errorCode);
conditionValues.Add(this.GetRangeMinItemKey(operation.ItemKey));
conditionValues.Add(this.GetRangeMaxItemKey(operation.ItemKey));
action = string.Format(RedisLuaCommands.FeedInsertIfNotEmptyAction, resultIndex, keyIndex, valueIndex + 2);
actionValues.Add(this.GetValueBytes(operation));
exceptions.Add(new ConflictException(Strings.Conflict, -errorCode - 1, null));
resultETag = this.ConvertValueToETag(actionValues.Last());
trimCheck = true;
break;
}
if (trimCheck)
{
FeedTable feedTable = operation.Table as FeedTable;
if (feedTable.MaxFeedSizeInCache != int.MaxValue)
{
if (!feedTrims.ContainsKey(key))
{
feedTrims.Add(key, feedTable);
}
}
}
}
else if (operation.Table is CountTable)
{
switch (operation.OperationType)
{
case OperationType.Insert:
condition = string.Format(RedisLuaCommands.CountInsertCondition, keyIndex, errorCode);
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.CountInsertAction, resultIndex, keyIndex, script.Values.Count + 1, script.Values.Count + actionValues.Count);
exceptions.Add(new ConflictException(Strings.Conflict, -errorCode - 1, null));
resultETag = operation.Entity.CustomETag;
break;
case OperationType.Delete:
if (operation.Entity == null || operation.Entity.ETag == "*")
{
condition = string.Format(RedisLuaCommands.CountDeleteCondition, keyIndex, errorCode);
action = string.Format(RedisLuaCommands.CountDeleteAction, resultIndex, keyIndex);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
}
else
{
condition = string.Format(RedisLuaCommands.CountETagCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(operation.Entity.ETag);
checkConditionValue = true;
action = string.Format(RedisLuaCommands.CountDeleteAction, resultIndex, keyIndex);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
exceptions.Add(new PreconditionFailedException(Strings.PreconditionFailed, -errorCode - 1, null));
}
break;
case OperationType.DeleteIfExists:
action = string.Format(RedisLuaCommands.CountDeleteAction, resultIndex, keyIndex);
break;
case OperationType.InsertOrReplace:
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.CountInsertOrReplaceAction, resultIndex, keyIndex, script.Values.Count + 1, script.Values.Count + actionValues.Count);
resultETag = operation.Entity.CustomETag;
break;
case OperationType.Replace:
if (operation.Entity.ETag == "*")
{
condition = string.Format(RedisLuaCommands.CountWildcardReplaceCondition, keyIndex, errorCode);
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.CountInsertOrReplaceAction, resultIndex, keyIndex, script.Values.Count + 1, script.Values.Count + actionValues.Count);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
resultETag = operation.Entity.CustomETag;
}
else
{
condition = string.Format(RedisLuaCommands.CountETagCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(operation.Entity.ETag);
checkConditionValue = true;
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.CountReplaceAction, resultIndex, keyIndex, script.Values.Count + 2, script.Values.Count + actionValues.Count + 1);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
exceptions.Add(new PreconditionFailedException(Strings.PreconditionFailed, -errorCode - 1, null));
resultETag = operation.Entity.CustomETag;
}
break;
case OperationType.Increment:
condition = string.Format(RedisLuaCommands.CountIncrementCondition, keyIndex, errorCode);
action = string.Format(RedisLuaCommands.CountIncrementAction, resultIndex, keyIndex, valueIndex, valueIndex + 1);
actionValues.Add(SpecialFieldNames.Count);
actionValues.Add(operation.Score);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
break;
case OperationType.InsertOrIncrement:
actionValues.AddRange(this.GetValueHash(operation.Entity));
action = string.Format(RedisLuaCommands.CountInsertOrIncrementAction, resultIndex, keyIndex, script.Values.Count + 1, script.Values.Count + actionValues.Count, script.Values.Count + actionValues.Count + 1, script.Values.Count + actionValues.Count + 2);
actionValues.Add(SpecialFieldNames.Count);
actionValues.Add(operation.Score);
resultETag = operation.Entity.CustomETag;
break;
}
}
else if (operation.Table is RankFeedTable)
{
bool trimCheck = false;
switch (operation.OperationType)
{
case OperationType.Insert:
condition = string.Format(RedisLuaCommands.RankFeedInsertCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(operation.ItemKey);
action = string.Format(RedisLuaCommands.RankFeedInsertAction, resultIndex, keyIndex, valueIndex, valueIndex + 1);
actionValues.Add(operation.Score);
exceptions.Add(new ConflictException(Strings.Conflict, -errorCode - 1, null));
trimCheck = true;
break;
case OperationType.Delete:
condition = string.Format(RedisLuaCommands.RankFeedDeleteCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(operation.ItemKey);
action = string.Format(RedisLuaCommands.RankFeedDeleteAction, resultIndex, keyIndex, valueIndex);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
break;
case OperationType.DeleteIfExists:
action = string.Format(RedisLuaCommands.RankFeedDeleteAction, resultIndex, keyIndex, valueIndex);
actionValues.Add(operation.ItemKey);
break;
case OperationType.InsertOrReplace:
action = string.Format(RedisLuaCommands.RankFeedInsertOrReplaceAction, resultIndex, keyIndex, valueIndex, valueIndex + 1);
actionValues.Add(operation.ItemKey);
actionValues.Add(operation.Score);
trimCheck = true;
break;
case OperationType.Increment:
condition = string.Format(RedisLuaCommands.RankFeedIncrementCondition, keyIndex, valueIndex, errorCode);
conditionValues.Add(operation.ItemKey);
action = string.Format(RedisLuaCommands.RankFeedIncrementAction, resultIndex, keyIndex, valueIndex, valueIndex + 1);
actionValues.Add(operation.Score);
exceptions.Add(new NotFoundException(Strings.NotFound, -errorCode - 1, null));
break;
case OperationType.InsertOrIncrement:
action = string.Format(RedisLuaCommands.RankFeedIncrementAction, resultIndex, keyIndex, valueIndex, valueIndex + 1);
actionValues.Add(operation.ItemKey);
actionValues.Add(operation.Score);
trimCheck = true;
break;
}
if (trimCheck)
{
RankFeedTable rankFeedTable = operation.Table as RankFeedTable;
if (rankFeedTable.MaxFeedSizeInCache != int.MaxValue)
{
if (!feedTrims.ContainsKey(key))
{
feedTrims.Add(key, rankFeedTable);
}
}
}
}
script.Keys.Add(key);
keyIndex++;
if (condition != null)
{
script.Conditions.Add(condition);
}
if (conditionValues.Count > 0)
{
script.Values.AddRange(conditionValues);
valueIndex += conditionValues.Count;
}
else
{
if (checkConditionValue)
{
throw new ArgumentException(string.Format("Operation requires an ETag (which may be the '*' wildcard)."));
}
}
script.Actions.Add(action);
if (actionValues.Count > 0)
{
script.Values.AddRange(actionValues);
valueIndex += actionValues.Count;
}
script.ETags.Add(resultETag);
if (exceptions.Count > 0)
{
script.Exceptions.Add(errorCode, exceptions);
}
errorCode--;
resultIndex++;
}
foreach (string key in feedTrims.Keys)
{
Table table = feedTrims[key];
string actionString = null;
int maxFeedSizeInCache = int.MaxValue;
if (table is FeedTable)
{
FeedTable feedTable = table as FeedTable;
actionString = RedisLuaCommands.FeedTrimAction;
maxFeedSizeInCache = feedTable.MaxFeedSizeInCache;
}
else if (table is RankFeedTable)
{
RankFeedTable rankFeedTable = table as RankFeedTable;
actionString = rankFeedTable.Order == FeedOrder.Ascending ? RedisLuaCommands.RankFeedAscendingTrimAction : RedisLuaCommands.RankFeedDescendingTrimAction;
maxFeedSizeInCache = rankFeedTable.MaxFeedSizeInCache;
}
if (actionString != null)
{
script.Actions.Add(string.Format(actionString, keyIndex, valueIndex));
script.Keys.Add(key);
script.Values.Add(maxFeedSizeInCache);
keyIndex++;
valueIndex++;
}
}
script.Script = string.Join(
" ",
string.Join(" ", script.Conditions),
RedisLuaCommands.ResultArray,
string.Join(" ", script.Actions),
RedisLuaCommands.LuaReturnSuccess);
return script;
}