diff --git a/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/ExpressionBuilder.cs b/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/ExpressionBuilder.cs new file mode 100644 index 000000000000..c6023d437269 --- /dev/null +++ b/sdk/src/Services/DynamoDBv2/Custom/DocumentModel/ExpressionBuilder.cs @@ -0,0 +1,1595 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +/* + * This file contains classes and utilities for building expressions used in DynamoDB operations. + * It provides a flexible and extensible framework for constructing conditional, filter, and query expressions + * using attribute names, values, and logical operators. + */ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Amazon.Runtime; + +namespace Amazon.DynamoDBv2.DocumentModel +{ + /// + /// Interface for building expressions used in DynamoDB operations. + /// + public interface IExpressionBuilder + { + /// + /// Builds the expression based on the conditions and operands provided. + /// + /// An object representing the constructed expression. + Expression Build(); + } + + /// + /// Abstract base class for building expressions used in DynamoDB operations. + /// Provides a structure for managing conditions and operands that form the basis of an expression. + /// + public abstract class ExpressionBuilder: IExpressionBuilder + { + /// + /// Builds the expression based on provided. + /// + /// An object representing the constructed expression. + public Expression Build() + { + var expressionTree = BuildExpressionTree(out string expressionType); + + var aliasList = new KeyAttributeAliasList(); + + var expression = new Expression() + { + ExpressionStatement = expressionTree.BuildExpressionString(aliasList, expressionType) + }; + + if (aliasList.NamesList != null && aliasList.NamesList.Count != 0) + { + var namesDictionary = new Dictionary(); + for (int i = 0; i < aliasList.NamesList.Count; i++) + { + namesDictionary[$"#{expressionType}{i}"] = aliasList.NamesList[i]; + } + + expression.ExpressionAttributeNames = namesDictionary; + } + + if (aliasList.ValuesList != null && aliasList.ValuesList.Count != 0) + { + var values = new Dictionary(); + for (int i = 0; i < aliasList.ValuesList.Count; i++) + { + values[$":{expressionType}{i}"] = aliasList.ValuesList[i]; + } + + expression.ExpressionAttributeValues = values; + } + + return expression; + } + + /// + /// Builds the expression tree for the current expression builder. + /// + /// + /// + internal abstract ExpressionNode BuildExpressionTree(out string s); + } + + /// + /// The class is used to construct update expressions for DynamoDB operations. + /// An update expression consists of one or more clauses. Each clause begins with a SET, REMOVE, ADD, or DELETE keyword. + /// You can include any of these clauses in an update expression, in any order. + /// + public class UpdateExpressionBuilder : ExpressionBuilder + { + + private const string OperationModeSet = "SET"; + private const string OperationModeRemove = "REMOVE"; + private const string OperationModeAdd = "ADD"; + private const string OperationModeDelete = "DELETE"; + + /// + /// The list of operations that will be used in the update expression. + /// + private Dictionary> OperationBuilders { get; set; } + + /// + /// Initializes a new instance of the class with default settings. + /// + protected UpdateExpressionBuilder() + { + OperationBuilders = new Dictionary>(); + } + + /// + /// Creates a new instance of the class. + /// + /// A new instance for building update expressions. + public static UpdateExpressionBuilder New() + { + return new UpdateExpressionBuilder(); + } + + /// + /// Adds a "SET" operation to the update expression, which sets the value of an attribute. + /// + /// The representing the attribute name. + /// The representing the value to set. + /// The current instance for chaining additional operations. + public UpdateExpressionBuilder Set(NameBuilder nameBuilder, OperandBuilder setExpressionBuilder) + { + if (!OperationBuilders.TryGetValue(OperationModeSet, out var ops)) + { + ops = new List(); + OperationBuilders[OperationModeSet] = ops; + } + + ops.Add(new OperationBuilder(OperationMode.Set) + { + Name = nameBuilder, + Value = setExpressionBuilder + }); + + return this; + } + + /// + /// Adds a "REMOVE" operation to the update expression, which removes one or more attributes from an item. + /// + /// The representing the attribute name to remove. + /// The current instance for chaining additional operations. + public UpdateExpressionBuilder Remove(NameBuilder nameBuilder) + { + if (!OperationBuilders.TryGetValue(OperationModeRemove, out var ops)) + { + ops = new List(); + OperationBuilders[OperationModeRemove] = ops; + } + + ops.Add(new OperationBuilder(OperationMode.Remove) + { + Name = nameBuilder + }); + + return this; + } + + /// + /// Adds a "DELETE" operation to the update expression, which removes one or more elements from a set attribute. + /// + /// The representing the attribute name. + /// The representing the value to delete from the set. + /// The current instance for chaining additional operations. + public UpdateExpressionBuilder Delete(NameBuilder nameBuilder, ValueBuilder valueBuilder) + { + if (!OperationBuilders.TryGetValue(OperationModeDelete, out var ops)) + { + ops = new List(); + OperationBuilders[OperationModeDelete] = ops; + } + + ops.Add(new OperationBuilder(OperationMode.Delete) + { + Name = nameBuilder, + Value = valueBuilder + }); + + return this; + } + + /// + /// Adds an "ADD" operation to the update expression, which adds a new attribute or modifies an existing one. + /// If the attribute is a number, the value is added or subtracted. If the attribute is a set, the value is appended. + /// + /// The representing the attribute name. + /// The representing the value to add. + /// The current instance for chaining additional operations. + public UpdateExpressionBuilder Add(NameBuilder nameBuilder, ValueBuilder valueBuilder) + { + if (!OperationBuilders.TryGetValue(OperationModeAdd, out var ops)) + { + ops = new List(); + OperationBuilders[OperationModeAdd] = ops; + } + + ops.Add(new OperationBuilder(OperationMode.Add) + { + Name = nameBuilder, + Value = valueBuilder + }); + + return this; + } + + /// + /// Builds the expression tree for the update expression based on the operations provided. + /// + /// + /// An representing the constructed update expression tree. + /// Thrown if no operations are defined in the update expression. + internal override ExpressionNode BuildExpressionTree(out string s) + { + s = "U"; + var resultNode = new ExpressionNode + { + Children = new Queue() + }; + + if (OperationBuilders.TryGetValue(OperationModeSet, out var setOps)) + { + var childNode = BuildChildNodes(setOps); + resultNode.Children.Enqueue(childNode); + resultNode.FormatedExpression += $"{OperationModeSet} $c\n"; + } + + if (OperationBuilders.TryGetValue(OperationModeRemove, out var removeOps)) + { + var childNode = BuildChildNodes(removeOps); + resultNode.Children.Enqueue(childNode); + resultNode.FormatedExpression += $"{OperationModeRemove} $c\n"; + } + + if (OperationBuilders.TryGetValue(OperationModeAdd, out var addOps)) + { + var childNode = BuildChildNodes(addOps); + resultNode.Children.Enqueue(childNode); + resultNode.FormatedExpression += $"{OperationModeAdd} $c\n"; + } + + if (OperationBuilders.TryGetValue(OperationModeDelete, out var deleteOps)) + { + var childNode = BuildChildNodes(deleteOps); + resultNode.Children.Enqueue(childNode); + resultNode.FormatedExpression += $"{OperationModeDelete} $c\n"; + } + + if (resultNode.Children.Count == 0) + { + throw new InvalidOperationException("Cannot build update expression tree: operation list is empty."); + } + + return resultNode; + } + + private ExpressionNode BuildChildNodes(List builders) + { + if (builders == null || builders.Count == 0) + { + throw new InvalidOperationException("Cannot build update expression tree: operation list is empty."); + } + + var node = new ExpressionNode + { + Children = new Queue() + }; + + foreach (var builder in builders) + { + var expr = builder.Build(); + + node.Children.Enqueue(expr); + node.FormatedExpression += "$c, "; + } + + // Remove trailing comma and space, if any + if (node.FormatedExpression.EndsWith(", ")) + { + node.FormatedExpression = node.FormatedExpression.Substring(0, node.FormatedExpression.Length - 2); + } + + return node; + } + + } + + /// + /// Class for building conditions used in DynamoDB expressions. + /// Supports comparison, logical, and function-based conditions. + /// + public class ConditionExpressionBuilder : ExpressionBuilder + { + private readonly ConditionMode _conditionMode; + + /// + /// The list of conditions that will be used in the expression. + /// + internal List Conditions { get; set; } + + /// + /// The list of operands that will be used in the expression. + /// + internal List Operands { get; set; } + + /// + /// Initializes a new instance of the class with default settings. + /// + protected ConditionExpressionBuilder() + { + _conditionMode = ConditionMode.Unset; + Operands = new List(); + Conditions = new List(); + } + + private ConditionExpressionBuilder(List operandBuilders, ConditionMode mode) : this() + { + + _conditionMode = mode; + Operands = operandBuilders; + } + + private ConditionExpressionBuilder(List conditionBuilders, ConditionMode mode) : this() + { + _conditionMode = mode; + Conditions = conditionBuilders; + } + + /// + /// Creates a new instance of . + /// + /// A new instance. + public static ConditionExpressionBuilder New() + { + return new ConditionExpressionBuilder(); + } + + /// + /// Adds a condition to the current condition builder. + /// + /// The condition to add. + /// The current instance for chaining. + public ConditionExpressionBuilder WithCondition(ConditionExpressionBuilder condition) + { + Conditions.Add(condition); + return this; + } + + /// + /// Adds an attribute name to the condition. + /// + /// The attribute name or path. + /// A for further configuration. + public NameBuilder WithName(string path) + { + return new NameBuilder(path); + } + + /// + /// Combines multiple conditions using the logical "AND" operator. + /// + /// The first instance to combine. + /// The second instance to combine. + /// Additional instances to include in the combination. + /// A new instance representing the combined "AND" condition. + public static ConditionExpressionBuilder And(ConditionExpressionBuilder left, ConditionExpressionBuilder right, + params ConditionExpressionBuilder[] others) + { + var allConditions = new List { left, right }; + if (others is { Length: > 0 }) + { + allConditions.AddRange(others); + } + + return new ConditionExpressionBuilder(allConditions, ConditionMode.And); + } + + /// + /// Combines the current condition with additional conditions using the logical "AND" operator. + /// + /// The instance to combine with the current condition. + /// Additional instances to include in the combination. + /// A new instance representing the combined "AND" condition. + public ConditionExpressionBuilder And(ConditionExpressionBuilder right, params ConditionExpressionBuilder[] others) + { + var allConditions = new List { this, right }; + if (others is { Length: > 0 }) + { + allConditions.AddRange(others); + } + + return new ConditionExpressionBuilder(allConditions, ConditionMode.And); + } + + /// + /// Combines multiple conditions using the logical "OR" operator. + /// + /// The first instance to combine. + /// The second instance to combine. + /// Additional instances to include in the combination. + /// A new instance representing the combined "OR" condition. + public static ConditionExpressionBuilder Or(ConditionExpressionBuilder left, ConditionExpressionBuilder right, + params ConditionExpressionBuilder[] others) + { + var allConditions = new List { left, right }; + if (others is { Length: > 0 }) + { + allConditions.AddRange(others); + } + + return new ConditionExpressionBuilder(allConditions, ConditionMode.Or); + } + + /// + /// Combines the current condition with additional conditions using the logical "OR" operator. + /// + /// The instance to combine with the current condition. + /// Additional instances to include in the combination. + /// A new instance representing the combined "OR" condition. + public ConditionExpressionBuilder Or(ConditionExpressionBuilder right, params ConditionExpressionBuilder[] others) + { + var allConditions = new List { this, right }; + if (others is { Length: > 0 }) + { + allConditions.AddRange(others); + } + + return new ConditionExpressionBuilder(allConditions, ConditionMode.Or); + } + + /// + /// Negates a condition using the logical "NOT" operator. + /// + /// The instance to negate. + /// A new instance representing the negated condition. + public static ConditionExpressionBuilder Not(ConditionExpressionBuilder condition) + { + var allConditions = new List { condition }; + return new ConditionExpressionBuilder(allConditions, ConditionMode.Not); + } + + /// + /// Negates the current condition using the logical "NOT" operator. + /// + /// A new instance representing the negated condition. + public ConditionExpressionBuilder Not() + { + var allConditions = new List { this }; + return new ConditionExpressionBuilder(allConditions, ConditionMode.Not); + } + + internal static ConditionExpressionBuilder Equal(OperandBuilder left, OperandBuilder right) + { + var condition = new ConditionExpressionBuilder(new List { left, right }, ConditionMode.Equal); + return condition; + } + + internal static ConditionExpressionBuilder NotEqual(OperandBuilder left, OperandBuilder right) + { + var condition = new ConditionExpressionBuilder(new List { left, right }, ConditionMode.NotEqual); + return condition; + } + + internal static ConditionExpressionBuilder LesThan(OperandBuilder left, OperandBuilder right) + { + var condition = new ConditionExpressionBuilder(new List { left, right }, ConditionMode.LessThan); + return condition; + } + + internal static ConditionExpressionBuilder LessThanOrEqual(OperandBuilder left, OperandBuilder right) + { + var condition = new ConditionExpressionBuilder(new List { left, right }, ConditionMode.LessThanOrEqual); + return condition; + } + + internal static ConditionExpressionBuilder GreaterThan(OperandBuilder left, OperandBuilder right) + { + var condition = new ConditionExpressionBuilder(new List { left, right }, ConditionMode.GreaterThan); + return condition; + } + + internal static ConditionExpressionBuilder GreaterThanOrEqual(OperandBuilder left, OperandBuilder right) + { + var condition = new ConditionExpressionBuilder(new List { left, right }, ConditionMode.GreaterThanOrEqual); + return condition; + } + + internal static ConditionExpressionBuilder Between(OperandBuilder left, OperandBuilder lowerOperand, OperandBuilder upperOperand) + { + var condition = new ConditionExpressionBuilder(new List { left, lowerOperand, upperOperand }, ConditionMode.Between); + return condition; + } + + internal static ConditionExpressionBuilder In(OperandBuilder left, OperandBuilder rightOperand, params OperandBuilder[] otherOperands) + { + var operands = new List { left, rightOperand }; + if (otherOperands is { Length: > 0 }) + { + operands.AddRange(otherOperands); + } + var condition = new ConditionExpressionBuilder(operands, ConditionMode.In); + return condition; + } + + internal static ConditionExpressionBuilder AttributeExists(OperandBuilder nameOperand) + { + var condition = new ConditionExpressionBuilder(new List { nameOperand }, ConditionMode.AttributeExists); + return condition; + } + + internal static ConditionExpressionBuilder AttributeNotExists(OperandBuilder nameOperand) + { + var condition = new ConditionExpressionBuilder(new List { nameOperand }, ConditionMode.AttributeNotExists); + return condition; + } + internal static ConditionExpressionBuilder AttributeType(OperandBuilder left, OperandBuilder right) + { + var condition = new ConditionExpressionBuilder(new List { left, right }, ConditionMode.AttributeType); + return condition; + } + + internal static ConditionExpressionBuilder Contains(OperandBuilder left, OperandBuilder right) + { + var condition = new ConditionExpressionBuilder(new List { left, right }, ConditionMode.Contains); + return condition; + } + + internal static ConditionExpressionBuilder BeginsWith(OperandBuilder left, OperandBuilder right) + { + var condition = new ConditionExpressionBuilder(new List { left, right }, ConditionMode.BeginsWith); + return condition; + } + + /// + /// + internal override ExpressionNode BuildExpressionTree(out string s) + { + s = "C"; + var childNodes = BuildChildNodes(); + + var node = new ExpressionNode + { + Children = childNodes, + }; + + return _conditionMode switch + { + ConditionMode.Equal or + ConditionMode.NotEqual or + ConditionMode.LessThan or + ConditionMode.LessThanOrEqual or + ConditionMode.GreaterThan or + ConditionMode.GreaterThanOrEqual => + CompareBuildCondition(_conditionMode, node), + + ConditionMode.And or ConditionMode.Or => + CompoundBuildCondition(this, node), + + ConditionMode.Not => + NotBuildCondition(node), + + ConditionMode.Between => + BetweenBuildCondition(node), + + ConditionMode.In => + InBuildCondition(this, node), + + ConditionMode.AttributeExists => + AttributeExistsBuildCondition(node), + + ConditionMode.AttributeNotExists => + AttributeNotExistsBuildCondition(node), + + ConditionMode.AttributeType => + AttributeTypeBuildCondition(node), + + ConditionMode.BeginsWith => + BeginsWithBuildCondition(node), + + ConditionMode.Contains => + ContainsBuildCondition(node), + + ConditionMode.Unset => + throw new InvalidOperationException("ConditionBuilder"), + + _ => + throw new InvalidOperationException($"Build condition error: unsupported mode: {_conditionMode}") + }; + } + + private Queue BuildChildNodes() + { + var childNodes = new Queue(); + + foreach (var condition in Conditions) + { + ExpressionNode node; + try + { + node = condition.BuildExpressionTree(out _); + } + catch (Exception ex) + { + throw new InvalidOperationException("Failed to build condition tree", ex); + } + + childNodes.Enqueue(node); + } + + foreach (var operand in Operands) + { + ExpressionNode node; + try + { + node = operand.Build(); + } + catch (Exception ex) + { + throw new InvalidOperationException("Failed to build operand", ex); + } + + childNodes.Enqueue(node); + } + + return childNodes; + } + + #region statement_build + + private ExpressionNode NotBuildCondition(ExpressionNode node) + { + node.FormatedExpression = "NOT ($c)"; + return node; + } + + private ExpressionNode CompoundBuildCondition(ConditionExpressionBuilder conditionBuilder, ExpressionNode node) + { + var mode = conditionBuilder._conditionMode switch + { + ConditionMode.And => "AND", + ConditionMode.Or => "OR", + _ => throw new InvalidOperationException( + $"Unsupported condition operator: {conditionBuilder._conditionMode}") + }; + node.FormatedExpression = string.Join($" {mode} ", + node.Children.Select(c => "($c)")); + return node; + } + + private ExpressionNode CompareBuildCondition(ConditionMode conditionMode, ExpressionNode node) + { + switch (conditionMode) + { + case ConditionMode.Equal: + node.FormatedExpression = + $"$c = $c"; + break; + case ConditionMode.NotEqual: + node.FormatedExpression = + $"$c <> $c"; + break; + case ConditionMode.LessThan: + node.FormatedExpression = + $"$c < $c"; + break; + case ConditionMode.LessThanOrEqual: + node.FormatedExpression = + $"$c <= $c"; + break; + case ConditionMode.GreaterThan: + node.FormatedExpression = + $"$c > $c"; + break; + case ConditionMode.GreaterThanOrEqual: + node.FormatedExpression = + $"$c >= $c"; + break; + default: + throw new InvalidOperationException($"Unsupported mode: {conditionMode}"); + } + + return node; + } + + private ExpressionNode ContainsBuildCondition(ExpressionNode node) + { + node.FormatedExpression = "contains ($c, $c)"; + return node; + } + + private ExpressionNode BeginsWithBuildCondition(ExpressionNode node) + { + node.FormatedExpression = "begins_with ($c, $c)"; + return node; + } + + private ExpressionNode AttributeTypeBuildCondition(ExpressionNode node) + { + node.FormatedExpression = "attribute_type ($c, $c)"; + return node; + } + + private ExpressionNode AttributeNotExistsBuildCondition(ExpressionNode node) + { + node.FormatedExpression = "attribute_not_exists ($c)"; + return node; + } + + private ExpressionNode AttributeExistsBuildCondition(ExpressionNode node) + { + node.FormatedExpression = "attribute_exists ($c)"; + return node; + } + + private ExpressionNode InBuildCondition(ConditionExpressionBuilder conditionBuilder, ExpressionNode node) + { + node.FormatedExpression = "$c IN ("; + + for(int i = 1; i < node.Children.Count; i++){ + node.FormatedExpression += "$c, "; + } + // Remove trailing comma and space, if any + if (node.FormatedExpression.EndsWith(", ")) + { + node.FormatedExpression = node.FormatedExpression.Substring(0, node.FormatedExpression.Length - 2); + } + node.FormatedExpression += ")"; + return node; + } + + private ExpressionNode BetweenBuildCondition(ExpressionNode node) + { + node.FormatedExpression = "$c BETWEEN $c AND $c"; + return node; + } + + #endregion + + } + + /// + /// The class is responsible for building attribute names + /// for DynamoDB expressions. It allows the creation of conditions based on attribute names + /// and supports various comparison, logical, and function-based operations. + /// + public class NameBuilder : OperandBuilder + { + private readonly IEnumerable _names; + + /// + /// Initializes a new instance of the class with the specified attribute name. + /// + /// + /// The attribute name to be used in the expression. Supports nested attributes using dot notation + /// (e.g., "Parent.Child[0].Grandchild"). + /// + internal NameBuilder(string name) + { + if (!string.IsNullOrWhiteSpace(name)) + { + _names = name.Split('.'); + } + } + + /// + /// Creates a new instance of the class with the specified attribute name. + /// + /// + /// + public static NameBuilder New(string name) + { + return new NameBuilder(name); + } + + #region ConditionExpressionBuilder + + /// + /// Adds an attribute name to the condition. + /// + /// The attribute name or path. + /// A for further configuration. + public NameBuilder WithName(string path) + { + return new NameBuilder(path); + } + + /// + /// Creates an equal condition between the current attribute and the specified value. + /// + /// The value to compare against. + /// A representing the not-equal condition. + public ConditionExpressionBuilder Equal(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return ConditionExpressionBuilder.Equal(this, rightOperand); + } + + /// + /// Creates a not-equal condition between the current attribute and the specified value. + /// + /// The value to compare against. + /// A representing the not-equal condition. + public ConditionExpressionBuilder NotEqual(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return ConditionExpressionBuilder.NotEqual(this, rightOperand); + } + + /// + /// Creates a less-than condition between the current attribute and the specified value. + /// + /// The value to compare against. + /// A representing the less-than condition. + public ConditionExpressionBuilder LessThan(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return ConditionExpressionBuilder.LesThan(this, rightOperand); + } + + /// + /// Creates a less-than-or-equal condition between the current attribute and the specified value. + /// + /// The value to compare against. + /// A representing the less-than-or-equal condition. + public ConditionExpressionBuilder LessThanOrEqual(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return ConditionExpressionBuilder.LessThanOrEqual(this, rightOperand); + } + + /// + /// Creates a greater-than condition between the current attribute and the specified value. + /// + /// The value to compare against. + /// A representing the greater-than condition. + public ConditionExpressionBuilder GreaterThan(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return ConditionExpressionBuilder.GreaterThan(this, rightOperand); + } + + /// + /// Creates a greater-than-or-equal condition between the current attribute and the specified value. + /// + /// The value to compare against. + /// A representing the greater-than-or-equal condition. + public ConditionExpressionBuilder GreaterThanOrEqual(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return ConditionExpressionBuilder.GreaterThanOrEqual(this, rightOperand); + } + + /// + /// Creates a condition to check if the current attribute's value is between two specified values. + /// + /// The lower bound of the range. + /// The upper bound of the range. + /// A representing the between condition. + public ConditionExpressionBuilder Between(DynamoDBEntry lower, DynamoDBEntry upper) + { + var lowerOperand = new ValueBuilder(lower); + var upperOperand = new ValueBuilder(upper); + return ConditionExpressionBuilder.Between(this, lowerOperand,upperOperand); + } + + /// + /// Creates a condition to check if the current attribute's value is in a specified set of values. + /// + /// The first value in the set. + /// Additional values in the set. + /// A representing the in condition. + public ConditionExpressionBuilder In(DynamoDBEntry right, params DynamoDBEntry[] others) + { + var rightOperand = new ValueBuilder(right); + var otherOperands = others?.Select(other => new ValueBuilder(other)).Cast().ToArray(); + return ConditionExpressionBuilder.In(this, rightOperand, otherOperands); + } + + /// + /// Creates a condition to check if the current attribute exists in the DynamoDB item. + /// + /// A representing the attribute exists condition. + public ConditionExpressionBuilder AttributeExists() + { + return ConditionExpressionBuilder.AttributeExists(this); + } + + /// + /// + /// + /// + public ConditionExpressionBuilder AttributeNotExists() + { + return ConditionExpressionBuilder.AttributeNotExists(this); + } + + /// + /// Creates a condition to check if the current attribute has a specific DynamoDB attribute type. + /// + /// The expected DynamoDB attribute type. + /// A representing the attribute type condition. + public ConditionExpressionBuilder AttributeType(DynamoDBAttributeType type) + { + var rightOperand = new ValueBuilder(type.Value); + return ConditionExpressionBuilder.AttributeType(this,rightOperand); + } + + /// + /// Creates a condition to check if the current attribute's value begins with a specified prefix. + /// + /// The prefix to check for. + /// A representing the begins with condition. + public ConditionExpressionBuilder BeginsWith(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return ConditionExpressionBuilder.BeginsWith(this, rightOperand); + } + + /// + /// Creates a condition to check if the current attribute's value contains a specified substring or value. + /// + /// The substring or value to check for. + /// A representing the contains condition. + public ConditionExpressionBuilder Contains(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return ConditionExpressionBuilder.Contains(this, rightOperand); + } + + #endregion + + #region UpdateExpressionBuilder + + /// + /// Creates a "Plus" operation for the current attribute, which adds the specified value to the attribute. + /// + /// The value to add to the current attribute. + /// A representing the "Plus" operation. + public SetValueBuilder Plus(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return SetValueBuilder.Plus(this, rightOperand); + } + + /// + /// Creates a "Minus" operation for the current attribute, which subtracts the specified value from the attribute. + /// + /// The value to subtract from the current attribute. + /// A representing the "Minus" operation. + public SetValueBuilder Minus(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return SetValueBuilder.Minus(this, rightOperand); + } + + /// + /// Creates a "ListAppend" operation for the current attribute, which appends the specified value to a list attribute. + /// + /// The value to append to the list attribute. + /// A representing the "ListAppend" operation. + public SetValueBuilder ListAppend(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return SetValueBuilder.ListAppend(this, rightOperand); + } + + /// + /// Creates an "IfNotExists" operation for the current attribute, which sets the attribute to the specified value + /// if it does not already exist. + /// + /// The value to set if the attribute does not exist. + /// A representing the "IfNotExists" operation. + public SetValueBuilder IfNotExists(DynamoDBEntry setValue) + { + var rightOperand = new ValueBuilder(setValue); + return SetValueBuilder.IfNotExists(this, rightOperand); + } + + #endregion + + /// + /// Builds the expression node for the current attribute name. + /// + /// An representing the attribute name in the expression. + /// Thrown if the attribute name is invalid or unset. + internal override ExpressionNode Build() + { + if (_names==null || !_names.Any()) + throw new InvalidOperationException($"Unset name parameter"); + + var node = new ExpressionNode + { + Names = new Stack() + }; + + var fmtNames = new List(_names.Count()); + + foreach (var originalWord in _names) + { + if (string.IsNullOrEmpty(originalWord)) + throw new InvalidOperationException("Invalid parameter Name"); + + var word = originalWord; + var substr = string.Empty; + + // Ensure brackets are matched and contain only digits + var bracketPattern = new Regex(@"\[(\d+)\]"); + var bracketMatches = bracketPattern.Matches(word); + + if (word.Count(c => c == '[') != word.Count(c => c == ']')) + throw new InvalidOperationException("Invalid parameter Name"); + + foreach (Match match in bracketMatches) + { + if (!match.Success || match.Groups[1].Length == 0) + throw new InvalidOperationException("Invalid parameter Name"); + } + + // Extract suffix like [0][1] if present + if (word.EndsWith("]")) + { + for (int j = 0; j < word.Length; j++) + { + if (word[j] == '[') + { + substr = word.Substring(j); + word = word.Substring(0, j); + break; + } + } + } + + if (string.IsNullOrEmpty(word)) + throw new InvalidOperationException("Invalid parameter Name"); + + node.Names.Push(word); + fmtNames.Add($"$n{substr}"); + } + + node.FormatedExpression = string.Join(".", fmtNames); + + return node; + } + + } + + /// + /// ValueBuilder is used to build attribute values for expressions. + /// + public class ValueBuilder : OperandBuilder + { + private DynamoDBEntry _value; + + /// + /// Default constructor for ValueBuilder. + /// + /// + internal ValueBuilder(DynamoDBEntry value) + { + _value = value; + } + + /// + /// Creates a new instance of ValueBuilder with the specified value. + /// + /// + /// + public static ValueBuilder New(DynamoDBEntry value) + { + return new ValueBuilder(value); + } + + /// + /// Creates a "Plus" operation which adds the specified value to the current value. + /// + /// + /// + public SetValueBuilder Plus(OperandBuilder rightOperand) + { + return SetValueBuilder.Plus(this, rightOperand); + } + + /// + /// Creates a "Plus" operation which adds the specified value to the current value. + /// + /// + /// + public SetValueBuilder Plus(DynamoDBEntry right) + { + var rightOperand = new ValueBuilder(right); + return SetValueBuilder.Plus(this, rightOperand); + } + + /// + /// Creates a "Minus" operation which subtracts the specified value from the current value. + /// + /// + /// + public SetValueBuilder Minus(DynamoDBEntry right) + { + var rightOperand=new ValueBuilder(right); + return SetValueBuilder.Minus(this, rightOperand); + } + + /// + /// Creates a "Minus" operation which subtracts the specified value from the current value. + /// + /// + /// + public SetValueBuilder Minus(OperandBuilder rightOperand) + { + return SetValueBuilder.Minus(this, rightOperand); + } + + /// + /// + /// + /// + /// + public SetValueBuilder ListAppend(DynamoDBEntry right) + { + var rightOperand=new ValueBuilder(right); + return SetValueBuilder.ListAppend(this, rightOperand); + } + + /// + /// + /// + /// + /// + public SetValueBuilder ListAppend(OperandBuilder rightOperand) + { + return SetValueBuilder.ListAppend(this, rightOperand); + } + + + /// + /// + /// + /// + internal override ExpressionNode Build() + { + var values = new Stack(); + values.Push(_value); + return new ExpressionNode + { + Values = values, + FormatedExpression = "$v" + }; + } + } + + + /// + /// Abstract base class for building operands used in DynamoDB expressions. + /// Operands represent attribute names or values in an expression. + /// + public abstract class OperandBuilder + { + /// + /// Builds the operand into an for use in an expression. + /// + /// An representing the operand. + internal abstract ExpressionNode Build(); + } + + /// + /// Constants used for properties of type DynamoDB AttributeType. + /// + public class DynamoDBAttributeType : ConstantClass + { + /// + /// Constant S for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType S = new("S"); + + /// + /// Constant SS for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType SS = new("SS"); + + /// + /// Constant N for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType N = new("N"); + + /// + /// Constant NS for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType NS = new("NS"); + + /// + /// Constant B for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType B = new("B"); + + /// + /// Constant BS for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType BS = new("BS"); + + /// + /// Constant BOOL for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType BOOL = new("BOOL"); + + /// + /// Constant NULL for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType NULL = new("NULL"); + + /// + /// Constant L for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType L = new("L"); + + /// + /// Constant M for DynamoDBAttributeType + /// + public static readonly DynamoDBAttributeType M = new("M"); + + /// + /// + /// + /// + protected DynamoDBAttributeType(string value) + : base(value) + { + } + + /// + /// Finds the constant for the unique value. + /// + /// The unique value for the constant + /// The constant for the unique value + public static DynamoDBAttributeType FindValue(string value) + { + return FindValue(value); + } + + /// + /// Utility method to convert strings to the constant class. + /// + /// The string value to convert to the constant class. + /// + public static implicit operator DynamoDBAttributeType(string value) + { + return FindValue(value); + } + } + + /// + /// Enumeration of condition modes used in DynamoDB expressions. + /// Defines comparison, logical, and function-based conditions. + /// + internal enum ConditionMode + { + // Catches errors for unset ConditionBuilder structs + Unset = 0, + + // Comparison Conditions + Equal, + NotEqual, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual, + + // Logical Conditions + And, + Or, + Not, + + // Function-based Conditions + Between, + In, + AttributeExists, + AttributeNotExists, + AttributeType, + BeginsWith, + Contains + } + + internal enum OperationMode + { + Unset = 0, + Set, + Remove, + Add, + Delete + } + + /// + /// Abstract base class for building operands used in DynamoDB expressions. + /// Operands represent attribute names or values in an expression. + /// + internal class OperationBuilder + { + /// + /// Builds the operand into an for use in an expression. + /// + /// An representing the operand. + internal ExpressionNode Build() + { + var pathChild = Name.Build(); + + var node = new ExpressionNode + { + Children = new Queue(), + FormatedExpression = "$c" + }; + + node.Children.Enqueue(pathChild); + + if (Mode == OperationMode.Remove) + { + return node; + } + + var valueChild = Value.Build(); + + node.Children.Enqueue(valueChild); + + node.FormatedExpression += Mode switch + { + OperationMode.Set => " = $c", + OperationMode.Add or OperationMode.Delete => " $c", + _ => throw new InvalidOperationException( + $"Update expression construction failed: unsupported update operation mode: {Mode}") + }; + + return node; + } + + internal OperandBuilder Value { get; set; } + internal NameBuilder Name { get; set; } + internal OperationMode Mode { get; private set; } + + /// + /// + /// + /// + internal OperationBuilder(OperationMode mode) + { + Mode = mode; + } + } + + /// + /// Class for building set value expressions used in DynamoDB. + /// + public class SetValueBuilder: OperandBuilder + { + private OperandBuilder _leftOperand; + private OperandBuilder _rightOperand; + private SetValueMode _mode; + + private SetValueBuilder() + { + _mode = SetValueMode.Unset; + } + + /// + /// + /// + /// + public static SetValueBuilder New() + { + return new SetValueBuilder(); + } + + /// + /// Adds an attribute name to the condition. + /// + /// The attribute name or path. + /// A for further configuration. + public NameBuilder WithName(string path) + { + return new NameBuilder(path); + } + + /// + /// Creates a new ValueBuilder with the specified value. + /// + /// + /// + public ValueBuilder WithValue(DynamoDBEntry value) + { + return new ValueBuilder(value); + } + + internal static SetValueBuilder Plus(OperandBuilder left, OperandBuilder right) + { + return new SetValueBuilder() + { + _leftOperand = left, + _rightOperand = right, + _mode = SetValueMode.Plus + }; + } + internal static SetValueBuilder Minus(OperandBuilder left, OperandBuilder right) + { + return new SetValueBuilder() + { + _leftOperand = left, + _rightOperand = right, + _mode = SetValueMode.Minus + }; + } + + internal static SetValueBuilder ListAppend(OperandBuilder left, OperandBuilder right) + { + return new SetValueBuilder() + { + _leftOperand = left, + _rightOperand = right, + _mode = SetValueMode.ListAppend + }; + } + + internal static SetValueBuilder IfNotExists(OperandBuilder nameBuilder, OperandBuilder setValue) + { + return new SetValueBuilder() + { + _leftOperand = nameBuilder, + _rightOperand = setValue, + _mode = SetValueMode.IfNotExists + }; + } + + internal override ExpressionNode Build() + { + if (_mode == SetValueMode.Unset) + { + throw new ArgumentException("Cannot build operand: SetValueBuilder is in UnsetValue mode."); + } + + var left = _leftOperand.Build(); + + var right = _rightOperand.Build(); + + var node = new ExpressionNode() + { + Children = new Queue() + }; + node.Children.Enqueue(left); + node.Children.Enqueue(right); + + node.FormatedExpression = _mode switch + { + SetValueMode.Plus => "$c + $c", + SetValueMode.Minus => "$c - $c", + SetValueMode.ListAppend=> "list_append($c, $c)", + SetValueMode.IfNotExists => "if_not_exists($c, $c)", + _ => throw new InvalidOperationException($"Unsupported SetValueMode: '{_mode}'.") + }; + + return node; + } + } + + internal enum SetValueMode + { + Unset = 0, + Plus = 1, + Minus = 2, + ListAppend = 3, + IfNotExists = 4, + } + /// + /// Represents a node in an expression tree. + /// Used to construct the final expression string and manage attribute names and values. + /// + internal class ExpressionNode + { + /// + /// Stack of attribute names used in the expression. + /// + public Stack Names { get; set; } = new(); + + /// + /// Stack of attribute values used in the expression. + /// + public Stack Values { get; set; } = new(); + + /// + /// The formatted expression string for this node. + /// + public string FormatedExpression { get; set; } + + /// + /// Stack of child nodes representing sub-expressions. + /// + public Queue Children { get; set; } = new(); + + /// + /// Builds the final expression string for this node, including attribute aliases. + /// + /// A list of attribute aliases for names and values. + /// + /// The constructed expression string. + internal string BuildExpressionString(KeyAttributeAliasList aliasList, string expressionType) + { + var result = new StringBuilder(); + int i = 0; + + while (i < FormatedExpression.Length) + { + if (FormatedExpression[i] == '$' && i + 1 < FormatedExpression.Length) + { + var next = FormatedExpression[i + 1]; + switch (next) + { + case 'n': + { + if (Names.Count == 0) + throw new InvalidOperationException("Missing name for $n"); + + string name = Names.Pop(); + string alias = $"#{expressionType}{aliasList.NamesList.Count}"; + aliasList.NamesList.Add(name); + result.Append(alias); + break; + } + case 'v': + { + if (Values.Count == 0) + throw new InvalidOperationException("Missing value for $v"); + + var val = Values.Pop(); + string alias = $":{expressionType}{aliasList.ValuesList.Count}"; + aliasList.ValuesList.Add(val); + result.Append(alias); + break; + } + case 'c': + { + if (Children.Count == 0) + throw new InvalidOperationException("Missing child for $c"); + + var child = Children.Dequeue(); + string subExpr = child.BuildExpressionString(aliasList, expressionType); + result.Append(subExpr); + break; + } + default: + result.Append(FormatedExpression[i]); // not a known placeholder + break; + } + i += 2; // skip the placeholder + } + else + { + result.Append(FormatedExpression[i]); + i++; + } + } + + return result.ToString(); + } + } + + /// + /// Represents a list of attribute aliases for names and values used in DynamoDB expressions. + /// + internal class KeyAttributeAliasList + { + /// + /// List of attribute names aliases used in the expression. + /// + public List NamesList { get; set; } = new(); + + /// + /// List of attribute values aliases used in the expression. + /// + public List ValuesList { get; set; } = new(); + } +} \ No newline at end of file diff --git a/sdk/test/Services/DynamoDBv2/UnitTests/AWSSDK.UnitTests.DynamoDBv2.NetFramework.csproj b/sdk/test/Services/DynamoDBv2/UnitTests/AWSSDK.UnitTests.DynamoDBv2.NetFramework.csproj index f958bebf82b6..7a0470aa49d1 100644 --- a/sdk/test/Services/DynamoDBv2/UnitTests/AWSSDK.UnitTests.DynamoDBv2.NetFramework.csproj +++ b/sdk/test/Services/DynamoDBv2/UnitTests/AWSSDK.UnitTests.DynamoDBv2.NetFramework.csproj @@ -1,4 +1,4 @@ - + true net472 diff --git a/sdk/test/Services/DynamoDBv2/UnitTests/Custom/ExpressionBuilderTests.cs b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/ExpressionBuilderTests.cs new file mode 100644 index 000000000000..47dc126af155 --- /dev/null +++ b/sdk/test/Services/DynamoDBv2/UnitTests/Custom/ExpressionBuilderTests.cs @@ -0,0 +1,434 @@ +using Amazon.DynamoDBv2.DocumentModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace AWSSDK_DotNet.UnitTests +{ + [TestClass] + public class ExpressionBuilderTests + { + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + [TestCategory("DynamoDBv2")] + public void Build_ShouldThrowExceptionWhenNoOperation() + { + var builder = UpdateExpressionBuilder.New().Build(); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildForSetOperation_ShouldReturnExpression() + { + var builder = UpdateExpressionBuilder.New(); + builder.Set(NameBuilder.New("test"), + SetValueBuilder.New().WithValue(10).Plus(20)); + + var expressionTree = builder.Build(); + + Assert.IsTrue(expressionTree.ExpressionStatement.Contains("SET")); + Assert.AreEqual(2, expressionTree.ExpressionAttributeValues.Count); + Assert.AreEqual(1, expressionTree.ExpressionAttributeNames.Count); + } + + [TestMethod] + public void BuildForRemoveOperation_ShouldReturnExpression() + { + var builder = UpdateExpressionBuilder.New(); + builder.Remove(NameBuilder.New("test")); + + var expressionTree = builder.Build(); + + Assert.IsTrue(expressionTree.ExpressionStatement.Contains("REMOVE")); + Assert.AreEqual(1, expressionTree.ExpressionAttributeNames.Count); + } + + [TestMethod] + public void BuildForAddOperation_ShouldReturnExpression() + { + var builder = UpdateExpressionBuilder.New(); + builder.Add(NameBuilder.New("Garbage"), ValueBuilder.New("asdf")); + + var expressionTree = builder.Build(); + + Assert.IsTrue(expressionTree.ExpressionStatement.Contains("ADD")); + Assert.AreEqual(1, expressionTree.ExpressionAttributeNames.Count); + Assert.AreEqual(1, expressionTree.ExpressionAttributeValues.Count); + } + + [TestMethod] + public void BuildForDeleteOperation_ShouldReturnExpression() + { + var builder = UpdateExpressionBuilder.New(); + builder.Delete(NameBuilder.New("test"), ValueBuilder.New(10)); + + var expressionTree = builder.Build(); + + Assert.IsTrue(expressionTree.ExpressionStatement.Contains("DELETE")); + Assert.AreEqual(1, expressionTree.ExpressionAttributeNames.Count); + Assert.AreEqual(1, expressionTree.ExpressionAttributeValues.Count); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void BuildForRemoveOperation_WithInvalidName_ShouldThrowException() + { + var builder = UpdateExpressionBuilder.New(); + builder.Remove(NameBuilder.New("")); + builder.Build(); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void UpdateExpressionBuilder_WithMultipleSetOperations_Should_Build_Correctly() + { + var updateExpression = UpdateExpressionBuilder.New(). + Set(NameBuilder.New("test"), + SetValueBuilder.New().WithValue(10).Plus(20)). + Set(NameBuilder.New("test2"), + SetValueBuilder.New().WithValue(20).Minus(30)). + Build(); + + Assert.AreEqual("SET #U0 = :U0 + :U1, #U1 = :U2 - :U3\n", updateExpression.ExpressionStatement); + Assert.AreEqual(2, updateExpression.ExpressionAttributeNames.Count); + Assert.AreEqual("test", updateExpression.ExpressionAttributeNames["#U0"]); + Assert.AreEqual("test2", updateExpression.ExpressionAttributeNames["#U1"]); + Assert.AreEqual(4, updateExpression.ExpressionAttributeValues.Count); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void UpdateExpressionBuilder_SetAndRemoveExpressionBuilder_Build_Correctly() + { + var updateExpression = UpdateExpressionBuilder.New(). + Set(NameBuilder.New("test"), + SetValueBuilder.New().WithValue(10).Plus(20)). + Remove(NameBuilder.New("test2")). + Build(); + + Assert.AreEqual("SET #U0 = :U0 + :U1\nREMOVE #U1\n" ,updateExpression.ExpressionStatement); + Assert.AreEqual(2, updateExpression.ExpressionAttributeNames.Count); + Assert.AreEqual(2, updateExpression.ExpressionAttributeValues.Count); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void ConditionExpressionBuilderTest() + { + var conditionExpression = ConditionExpressionBuilder.New().WithName("Age").Equal(21). + And(ConditionExpressionBuilder.New().WithName("Status").GreaterThan(1)).Build(); + + Assert.AreEqual("(#C0 = :C0) AND (#C1 > :C1)", conditionExpression.ExpressionStatement); + Assert.AreEqual(2, conditionExpression.ExpressionAttributeNames.Count); + Assert.AreEqual("Age", conditionExpression.ExpressionAttributeNames["#C0"]); + Assert.AreEqual("Status", conditionExpression.ExpressionAttributeNames["#C1"]); + Assert.AreEqual(2, conditionExpression.ExpressionAttributeValues.Count); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void NameBuilderConstructor_WhenCalled_CreatesValidInstance() + { + var nameBuilder = NameBuilder.New("test"); + + Assert.IsNotNull(nameBuilder); + Assert.IsInstanceOfType(nameBuilder, typeof(OperandBuilder)); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void Complex_Attribute_Paths_Should_Build_Correctly() + { + var nameBuilder = NameBuilder.New("parent.child[0].attribute"); + var node = nameBuilder.AttributeExists().Build(); + + Assert.AreEqual("attribute_exists (#C0.#C1[0].#C2)", node.ExpressionStatement); + Assert.AreEqual(0, node.ExpressionAttributeValues.Count); + Assert.AreEqual(3, node.ExpressionAttributeNames.Count); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_WithSpecialCharacters_Should_Build_Correctly() + { + var attributeName = "Test#Attribute-Name.123"; + var nameBuilder = NameBuilder.New(attributeName); + + var result = nameBuilder.AttributeExists().Build(); + + Assert.IsNotNull(result); + Assert.AreEqual("attribute_exists (#C0.#C1)", result.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_Equal_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.Equal(10); + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("#C0 = :C0", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_NotEqual_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.NotEqual(10); + var resultNode = result.Build(); + + Assert.IsNotNull(result); + Assert.AreEqual("#C0 <> :C0", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_GreaterThan_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.GreaterThan(10); + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("#C0 > :C0", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_GreaterThanOrEqual_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.GreaterThanOrEqual(10); + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("#C0 >= :C0", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_LessThan_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.LessThan(10); + var resultNode = result.Build(); + + Assert.IsNotNull(result); + Assert.AreEqual("#C0 < :C0", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_LessThanOrEqual_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.LessThanOrEqual(10); + var resultNode = result.Build(); + + Assert.IsNotNull(result); + Assert.AreEqual("#C0 <= :C0", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_Between_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.Between(10, 20); + var resultNode = result.Build(); + + Assert.IsNotNull(result); + Assert.AreEqual("#C0 BETWEEN :C0 AND :C1", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_In_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.In(10, 20, 30); + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("#C0 IN (:C0, :C1, :C2)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_BeginsWith_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.BeginsWith("test"); + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("begins_with (#C0, :C0)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_Contains_WithValue_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.Contains("test"); + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("contains (#C0, :C0)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_AttributeExists_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.AttributeExists(); + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("attribute_exists (#C0)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_AttributeNotExists_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.AttributeNotExists(); + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("attribute_not_exists (#C0)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void BuildNameBuilder_AttributeType_ReturnsCondition() + { + var nameBuilder = NameBuilder.New("TestAttribute"); + + var result = nameBuilder.AttributeType(DynamoDBAttributeType.B); + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("attribute_type (#C0, :C0)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void ConditionExpressionBuilder_And_ReturnsCondition() + { + var result = ConditionExpressionBuilder. + And(NameBuilder.New("Attribute1").Equal(10), + NameBuilder.New("Attribute2").Equal(10)); + + var resultNode = result.Build(); + + Assert.IsNotNull(result); + Assert.AreEqual("(#C0 = :C0) AND (#C1 = :C1)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void ConditionExpressionBuilder_And_Multiple_ReturnsCondition() + { + + var result = ConditionExpressionBuilder.And(NameBuilder.New("Attribute1").Equal(10), + NameBuilder.New("Attribute2").Equal(10), + NameBuilder.New("Attribute3").Equal(10), + NameBuilder.New("Attribute4").Equal(10)); + + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("(#C0 = :C0) AND (#C1 = :C1) AND (#C2 = :C2) AND (#C3 = :C3)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void ConditionExpressionBuilder_Or_ReturnsCondition() + { + var result = ConditionExpressionBuilder.Or(NameBuilder.New("Attribute1").Equal(10), + NameBuilder.New("Attribute2").Equal(20)); + + var resultNode = result.Build(); + + Assert.IsNotNull(result); + Assert.AreEqual("(#C0 = :C0) OR (#C1 = :C1)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void ConditionExpressionBuilder_Or_Multiple_ReturnsCondition() + { + var result = ConditionExpressionBuilder.Or(NameBuilder.New("Attribute1").Equal(10), + NameBuilder.New("Attribute2").Equal(20), + NameBuilder.New("Attribute3").Equal(30), + NameBuilder.New("Attribute4").Equal(40)); + + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("(#C0 = :C0) OR (#C1 = :C1) OR (#C2 = :C2) OR (#C3 = :C3)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void ConditionExpressionBuilder_AndNestedOr_ReturnsCondition() + { + var result = ConditionExpressionBuilder. + And(NameBuilder.New("Attribute1").Equal(10), + ConditionExpressionBuilder.Or(NameBuilder.New("Attribute2").Equal(20), + NameBuilder.New("Attribute3").Equal(30))); + + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("(#C0 = :C0) AND ((#C1 = :C1) OR (#C2 = :C2))", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void ConditionExpressionBuilder_AndOr_ReturnsCondition() + { + var result = ConditionExpressionBuilder. + And(NameBuilder.New("Attribute1").Equal(10), + NameBuilder.New("Attribute2").Equal(20)).Or( + NameBuilder.New("Attribute3").Equal(30)); + + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("((#C0 = :C0) AND (#C1 = :C1)) OR (#C2 = :C2)", resultNode.ExpressionStatement); + } + + [TestMethod] + [TestCategory("DynamoDBv2")] + public void ConditionExpressionBuilder_Not_ReturnsCondition() + { + var result = ConditionExpressionBuilder. + Not(NameBuilder.New("Attribute1").Equal(10)); + + var resultNode = result.Build(); + + Assert.IsNotNull(resultNode); + Assert.AreEqual("NOT (#C0 = :C0)", resultNode.ExpressionStatement); + } + } + +} \ No newline at end of file diff --git a/sdk/test/UnitTests/Custom/AWSSDK.UnitTestUtilities.NetFramework.csproj b/sdk/test/UnitTests/Custom/AWSSDK.UnitTestUtilities.NetFramework.csproj index ac49aaa7d208..e1a59258e5e0 100644 --- a/sdk/test/UnitTests/Custom/AWSSDK.UnitTestUtilities.NetFramework.csproj +++ b/sdk/test/UnitTests/Custom/AWSSDK.UnitTestUtilities.NetFramework.csproj @@ -15,7 +15,6 @@ false false false - CS1591