Skip to content

Commit

Permalink
Query: Added remaining Cosmos Type checking functions to CosmosLinqEx…
Browse files Browse the repository at this point in the history
…tensions (#3724)

* Added the remaining Cosmos Type checking functions to the CosmosLinqExtensions

* Added comments requested

* Updated comment

* Updated baseline

* Improve readability of dictionary initialization

* Aligned with code style guide

* Revert change to baseline

* Executed update baseline script

---------

Co-authored-by: neildsh <35383880+neildsh@users.noreply.github.com>
Co-authored-by: leminh98 <leminh.ams@gmail.com>
  • Loading branch information
3 people authored May 9, 2023
1 parent 0c15865 commit ade7e34
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,62 @@ static TypeCheckFunctions()
{
TypeCheckFunctionsDefinitions = new Dictionary<string, BuiltinFunctionVisitor>
{
{
"IsDefined",
new SqlBuiltinFunctionVisitor("IS_DEFINED",
[nameof(CosmosLinqExtensions.IsArray)] = new SqlBuiltinFunctionVisitor(
"IS_ARRAY",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
})
},
{
"IsNull",
new SqlBuiltinFunctionVisitor("IS_NULL",
}),
[nameof(CosmosLinqExtensions.IsBool)] = new SqlBuiltinFunctionVisitor(
"IS_BOOL",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
})
},
{
"IsPrimitive",
new SqlBuiltinFunctionVisitor("IS_PRIMITIVE",
}),
[nameof(CosmosLinqExtensions.IsDefined)] = new SqlBuiltinFunctionVisitor(
"IS_DEFINED",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
})
}
}),
[nameof(CosmosLinqExtensions.IsNull)] = new SqlBuiltinFunctionVisitor(
"IS_NULL",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}),
[nameof(CosmosLinqExtensions.IsNumber)] = new SqlBuiltinFunctionVisitor(
"IS_NUMBER",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}),
[nameof(CosmosLinqExtensions.IsObject)] = new SqlBuiltinFunctionVisitor(
"IS_OBJECT",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}),
[nameof(CosmosLinqExtensions.IsPrimitive)] = new SqlBuiltinFunctionVisitor(
"IS_PRIMITIVE",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}),
[nameof(CosmosLinqExtensions.IsString)] = new SqlBuiltinFunctionVisitor(
"IS_STRING",
true,
new List<Type[]>()
{
new Type[]{typeof(object)},
}),
};
}

Expand Down
99 changes: 97 additions & 2 deletions Microsoft.Azure.Cosmos/src/Linq/CosmosLinqExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,44 @@ namespace Microsoft.Azure.Cosmos.Linq
/// </summary>
public static class CosmosLinqExtensions
{
/// <summary>
/// Returns a Boolean value indicating if the type of the specified expression is an array.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if the type of the specified expression is an array; otherwise, false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isArrayQuery = documents.Where(document => document.Names.IsArray());
/// ]]>
/// </code>
/// </example>
public static bool IsArray(this object obj)
{
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}

/// <summary>
/// Returns a Boolean value indicating if the type of the specified expression is a boolean.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if the type of the specified expression is a boolean; otherwise, false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isBoolQuery = documents.Where(document => document.IsRegistered.IsBool());
/// ]]>
/// </code>
/// </example>
public static bool IsBool(this object obj)
{
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}

/// <summary>
/// Determines if a certain property is defined or not.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
Expand Down Expand Up @@ -52,12 +90,50 @@ public static bool IsDefined(this object obj)
/// var isNullQuery = documents.Where(document => document.Name.IsNull());
/// ]]>
/// </code>
/// </example>s>
/// </example>
public static bool IsNull(this object obj)
{
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}

/// <summary>
/// Returns a Boolean value indicating if the type of the specified expression is a number.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if the type of the specified expression is a number; otherwise, false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isNumberQuery = documents.Where(document => document.Age.IsNumber());
/// ]]>
/// </code>
/// </example>
public static bool IsNumber(this object obj)
{
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}

/// <summary>
/// Returns a Boolean value indicating if the type of the specified expression is an object.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if the type of the specified expression is an object; otherwise, false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isObjectQuery = documents.Where(document => document.Address.IsObject());
/// ]]>
/// </code>
/// </example>
public static bool IsObject(this object obj)
{
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}

/// <summary>
/// Determines if a certain property is of primitive JSON type.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
Expand All @@ -74,12 +150,31 @@ public static bool IsNull(this object obj)
/// var isPrimitiveQuery = documents.Where(document => document.Name.IsPrimitive());
/// ]]>
/// </code>
/// </example>s>
/// </example>
public static bool IsPrimitive(this object obj)
{
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}

/// <summary>
/// Returns a Boolean value indicating if the type of the specified expression is a string.
/// This method is to be used in LINQ expressions only and will be evaluated on server.
/// There's no implementation provided in the client library.
/// </summary>
/// <param name="obj"></param>
/// <returns>Returns true if the type of the specified expression is a string; otherwise, false.</returns>
/// <example>
/// <code>
/// <![CDATA[
/// var isStringQuery = documents.Where(document => document.Name.IsString());
/// ]]>
/// </code>
/// </example>
public static bool IsString(this object obj)
{
throw new NotImplementedException(ClientResources.TypeCheckExtensionFunctionsNotImplemented);
}

/// <summary>
/// This method generate query definition from LINQ query.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,52 @@
<Results>
<Result>
<Input>
<Description><![CDATA[IsArray array]]></Description>
<Expression><![CDATA[query.Where(doc => doc.ArrayField.IsArray())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE IS_ARRAY(root["ArrayField"])]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsArray string]]></Description>
<Expression><![CDATA[query.Where(doc => doc.StringField.IsArray())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE IS_ARRAY(root["StringField"])]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsBool bool]]></Description>
<Expression><![CDATA[query.Where(doc => Convert(doc.BooleanField, Object).IsBool())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE IS_BOOL(root["BooleanField"])]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsBool string]]></Description>
<Expression><![CDATA[query.Where(doc => doc.StringField.IsBool())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE IS_BOOL(root["StringField"])]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsDefined array]]></Description>
Expand Down Expand Up @@ -45,6 +93,52 @@ FROM root
WHERE IS_NULL(root["StringField"])]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsNumber number]]></Description>
<Expression><![CDATA[query.Select(doc => Convert(doc.NumericField, Object).IsNumber())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE IS_NUMBER(root["NumericField"])
FROM root]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsNumber string]]></Description>
<Expression><![CDATA[query.Where(doc => doc.StringField.IsNumber())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE IS_NUMBER(root["StringField"])]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsObject object]]></Description>
<Expression><![CDATA[query.Select(doc => doc.ObjectField.IsObject())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE IS_OBJECT(root["ObjectField"])
FROM root]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsObject string]]></Description>
<Expression><![CDATA[query.Where(doc => doc.StringField.IsObject())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE IS_OBJECT(root["StringField"])]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsPrimitive array]]></Description>
Expand All @@ -68,4 +162,27 @@ FROM root
WHERE IS_PRIMITIVE(root["StringField"])]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsString string]]></Description>
<Expression><![CDATA[query.Where(doc => doc.StringField.IsString())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE root
FROM root
WHERE IS_STRING(root["StringField"])]]></SqlQuery>
</Output>
</Result>
<Result>
<Input>
<Description><![CDATA[IsString number]]></Description>
<Expression><![CDATA[query.Select(doc => Convert(doc.NumericField, Object).IsString())]]></Expression>
</Input>
<Output>
<SqlQuery><![CDATA[
SELECT VALUE IS_STRING(root["NumericField"])
FROM root]]></SqlQuery>
</Output>
</Result>
</Results>
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ internal class DataObject : LinqTestObject
public int? NullableField;
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value false
public bool BooleanField;
public SimpleObject ObjectField = new SimpleObject();
public Guid GuidField;
#pragma warning restore // Field is never assigned to, and will always have its default value false

Expand Down Expand Up @@ -155,6 +156,11 @@ internal class DataObject : LinqTestObject
public string Pk;
}

internal class SimpleObject
{
public string Field { get; set; }
}

class DateJsonConverter : IsoDateTimeConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
Expand Down Expand Up @@ -257,7 +263,7 @@ public void TestLiteralSerialization()
[TestMethod]
public void TestTypeCheckFunctions()
{
// IsDefined, IsNull, and IsPrimitive are not supported on the client side.
// IsArray, IsBool, IsDefined, IsNull, IsNumber, IsObject, IsPrimitive, and IsString are not supported on the client side.
// Partly because IsPrimitive is not trivial to implement.
// Therefore these methods are verified with baseline only.
List<DataObject> data = new List<DataObject>();
Expand All @@ -266,12 +272,22 @@ public void TestTypeCheckFunctions()

List<LinqTestInput> inputs = new List<LinqTestInput>
{
new LinqTestInput("IsArray array", b => getQuery(b).Where(doc => doc.ArrayField.IsArray())),
new LinqTestInput("IsArray string", b => getQuery(b).Where(doc => doc.StringField.IsArray())),
new LinqTestInput("IsBool bool", b => getQuery(b).Where(doc => doc.BooleanField.IsBool())),
new LinqTestInput("IsBool string", b => getQuery(b).Where(doc => doc.StringField.IsBool())),
new LinqTestInput("IsDefined array", b => getQuery(b).Select(doc => doc.ArrayField.IsDefined())),
new LinqTestInput("IsDefined string", b => getQuery(b).Where(doc => doc.StringField.IsDefined())),
new LinqTestInput("IsNull array", b => getQuery(b).Select(doc => doc.ArrayField.IsNull())),
new LinqTestInput("IsNull string", b => getQuery(b).Where(doc => doc.StringField.IsNull())),
new LinqTestInput("IsNumber number", b => getQuery(b).Select(doc => doc.NumericField.IsNumber())),
new LinqTestInput("IsNumber string", b => getQuery(b).Where(doc => doc.StringField.IsNumber())),
new LinqTestInput("IsObject object", b => getQuery(b).Select(doc => doc.ObjectField.IsObject())),
new LinqTestInput("IsObject string", b => getQuery(b).Where(doc => doc.StringField.IsObject())),
new LinqTestInput("IsPrimitive array", b => getQuery(b).Select(doc => doc.ArrayField.IsPrimitive())),
new LinqTestInput("IsPrimitive string", b => getQuery(b).Where(doc => doc.StringField.IsPrimitive()))
new LinqTestInput("IsPrimitive string", b => getQuery(b).Where(doc => doc.StringField.IsPrimitive())),
new LinqTestInput("IsString string", b => getQuery(b).Where(doc => doc.StringField.IsString())),
new LinqTestInput("IsString number", b => getQuery(b).Select(doc => doc.NumericField.IsString())),
};
this.ExecuteTestSuite(inputs);
}
Expand Down
Loading

0 comments on commit ade7e34

Please sign in to comment.