Skip to content

Commit

Permalink
Merge branch 'master' into users/ealsur/gatewayrequestst
Browse files Browse the repository at this point in the history
  • Loading branch information
ealsur committed Jan 3, 2022
2 parents 6744540 + 5375929 commit 5ae77bb
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 11 deletions.
12 changes: 8 additions & 4 deletions Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ public static SqlQuery TranslateQuery(
/// </summary>
/// <param name="inputExpression">Expression to translate.</param>
/// <param name="context">Context for translation.</param>
public static Collection Translate(Expression inputExpression, TranslationContext context)
private static Collection Translate(Expression inputExpression, TranslationContext context)
{
Debug.Assert(context != null, "Translation Context should not be null");

if (inputExpression == null)
{
throw new ArgumentNullException("inputExpression");
Expand Down Expand Up @@ -738,19 +740,21 @@ private static SqlScalarExpression VisitParameter(ParameterExpression inputExpre
private static SqlScalarExpression VisitMemberAccess(MemberExpression inputExpression, TranslationContext context)
{
SqlScalarExpression memberExpression = ExpressionToSql.VisitScalarExpression(inputExpression.Expression, context);
string memberName = inputExpression.Member.GetMemberName(context?.linqSerializerOptions);
string memberName = inputExpression.Member.GetMemberName(context.linqSerializerOptions);

// if expression is nullable
if (inputExpression.Expression.Type.IsNullable())
{
MemberNames memberNames = context.memberNames;

// ignore .Value
if (memberName == "Value")
if (memberName == memberNames.Value)
{
return memberExpression;
}

// convert .HasValue to IS_DEFINED expression
if (memberName == "HasValue")
if (memberName == memberNames.HasValue)
{
return SqlFunctionCallScalarExpression.CreateBuiltin("IS_DEFINED", memberExpression);
}
Expand Down
35 changes: 29 additions & 6 deletions Microsoft.Azure.Cosmos/src/Linq/TranslationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ namespace Microsoft.Azure.Cosmos.Linq
/// </summary>
internal sealed class TranslationContext
{
/// <summary>
/// Member names for special mapping cases
/// </summary>
internal readonly MemberNames memberNames;

/// <summary>
/// Set of parameters in scope at any point; used to generate fresh parameter names if necessary.
/// </summary>
Expand Down Expand Up @@ -53,7 +58,7 @@ internal sealed class TranslationContext
/// </summary>
private Stack<SubqueryBinding> subqueryBindingStack;

public TranslationContext()
public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDictionary<object, string> parameters = null)
{
this.InScope = new HashSet<ParameterExpression>();
this.substitutions = new ParameterSubstitution();
Expand All @@ -62,13 +67,9 @@ public TranslationContext()
this.collectionStack = new List<Collection>();
this.currentQuery = new QueryUnderConstruction(this.GetGenFreshParameterFunc());
this.subqueryBindingStack = new Stack<SubqueryBinding>();
}

public TranslationContext(CosmosLinqSerializerOptions linqSerializerOptions, IDictionary<object, string> parameters = null)
: this()
{
this.linqSerializerOptions = linqSerializerOptions;
this.parameters = parameters;
this.memberNames = new MemberNames(linqSerializerOptions);
}

public CosmosLinqSerializerOptions linqSerializerOptions;
Expand Down Expand Up @@ -339,6 +340,28 @@ public Expression Lookup(ParameterExpression parameter)
public const string InputParameterName = "root";
}

/// <summary>
/// Special member names for mapping
/// </summary>
internal sealed class MemberNames
{
internal MemberNames(CosmosLinqSerializerOptions options)
{
this.Value = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(options, nameof(this.Value));
this.HasValue = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(options, nameof(this.HasValue));
}

/// <summary>
/// HasValue for mapping <see cref="Nullable{T}.Value"/>
/// </summary>
public string Value { get; }

/// <summary>
/// HasValue for mapping <see cref="Nullable{T}.HasValue"/>
/// </summary>
public string HasValue { get; }
}

/// <summary>
/// Bindings for a set of parameters used in a FROM expression.
/// Each parameter is bound to a collection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,64 @@ static void builder(CosmosClientBuilder action)
Assert.AreEqual(queriable.Count(), 2);
}

[TestMethod]
public async Task LinqHasValue()
{
IList<ToDoActivity> itemList = await ToDoActivity.CreateRandomItems(this.Container, pkCount: 2, perPKItemCount: 1, randomPartitionKey: true);

CosmosLinqSerializerOptions camelCaseSerialization = new CosmosLinqSerializerOptions
{
PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
};
CosmosLinqSerializerOptions defaultSerialization = new CosmosLinqSerializerOptions
{
PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default
};
CosmosLinqSerializerOptions[] serializerOptions = new[] { null, camelCaseSerialization, defaultSerialization };

foreach(CosmosLinqSerializerOptions linqSerializerOptions in serializerOptions)
{
IOrderedQueryable<ToDoActivity> linqQueryable = this.Container.GetItemLinqQueryable<ToDoActivity>(true, null, null, linqSerializerOptions);
// Nullable<T>.HasValue translates to IS_DEFINED - should work regardless of serialization/casing
IQueryable<ToDoActivity> queriable = linqQueryable.Where(item => item.nullableInt.HasValue);
Assert.AreEqual(2, queriable.Count(), "HasValue should have returned two items.");
}
}

[TestMethod]
public async Task LinqWithIgnoreNullValuesHasValueNoResults()
{
static void builder(CosmosClientBuilder action)
{
action.WithSerializerOptions(new CosmosSerializationOptions()
{
IgnoreNullValues = true
});
}
CosmosClient nullValuesClient = TestCommon.CreateCosmosClient(builder, false);
Cosmos.Database database = nullValuesClient.GetDatabase(this.database.Id);
Container containerFromNulValuesClient = database.GetContainer(this.Container.Id);

IList<ToDoActivity> itemList = await ToDoActivity.CreateRandomItems(containerFromNulValuesClient, pkCount: 2, perPKItemCount: 1, randomPartitionKey: true);

CosmosLinqSerializerOptions camelCaseSerialization = new CosmosLinqSerializerOptions
{
PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
};
CosmosLinqSerializerOptions defaultSerialization = new CosmosLinqSerializerOptions
{
PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default
};
CosmosLinqSerializerOptions[] serializerOptions = new[] { null, camelCaseSerialization, defaultSerialization };

foreach (CosmosLinqSerializerOptions linqSerializerOptions in serializerOptions)
{
IOrderedQueryable<ToDoActivity> linqQueryable = this.Container.GetItemLinqQueryable<ToDoActivity>(true, null, null, linqSerializerOptions);
IQueryable<ToDoActivity> queriable = linqQueryable.Where(item => item.nullableInt.HasValue);
Assert.AreEqual(0, queriable.Count(), "HasValue should have returned zero items.");
}
}

[TestMethod]
public async Task LinqParameterisedTest1()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class ToDoActivity
public string description { get; set; }
public string pk { get; set; }
public string CamelCase { get; set; }
public int? nullableInt { get; set; }

public bool valid { get; set; }

Expand Down Expand Up @@ -90,7 +91,8 @@ public static ToDoActivity CreateRandomToDoActivity(string pk = null, string id
{ new ToDoActivity { id = "child1", taskNum = 30 },
new ToDoActivity { id = "child2", taskNum = 40}
},
valid = true
valid = true,
nullableInt = null
};
}
}
Expand Down

0 comments on commit 5ae77bb

Please sign in to comment.