Skip to content

Commit

Permalink
[Internal] Query: Adds interface for linq serialization functions (#4163
Browse files Browse the repository at this point in the history
)

* intial commit

* add interface

* PR comments and TranslationContext cleanup

* update params

* fix parameters

* PR comments

* PR comments

* PR comments

* simplifying serializer class

* interface updates

* Update docs

* PR comments

* PR comments

* PR comments - rename and fix assert
  • Loading branch information
Maya-Painter authored Nov 8, 2023
1 parent 4db6078 commit d287f08
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 246 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Linq
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.Azure.Cosmos.CosmosElements;
using Microsoft.Azure.Cosmos.CosmosElements.Numbers;
using Microsoft.Azure.Cosmos.SqlObjects;

internal sealed class CosmosElementToSqlScalarExpressionVisitor : ICosmosElementVisitor<SqlScalarExpression>
{
public static readonly CosmosElementToSqlScalarExpressionVisitor Singleton = new CosmosElementToSqlScalarExpressionVisitor();

private CosmosElementToSqlScalarExpressionVisitor()
{
// Private constructor, since this class is a singleton.
}

public SqlScalarExpression Visit(CosmosArray cosmosArray)
{
List<SqlScalarExpression> items = new List<SqlScalarExpression>();
foreach (CosmosElement item in cosmosArray)
{
items.Add(item.Accept(this));
}

return SqlArrayCreateScalarExpression.Create(items.ToImmutableArray());
}

public SqlScalarExpression Visit(CosmosBinary cosmosBinary)
{
// Can not convert binary to scalar expression without knowing the API type.
Debug.Fail("CosmosElementToSqlScalarExpressionVisitor Assert", "Unreachable");
throw new InvalidOperationException();
}

public SqlScalarExpression Visit(CosmosBoolean cosmosBoolean)
{
return SqlLiteralScalarExpression.Create(SqlBooleanLiteral.Create(cosmosBoolean.Value));
}

public SqlScalarExpression Visit(CosmosGuid cosmosGuid)
{
// Can not convert guid to scalar expression without knowing the API type.
Debug.Fail("CosmosElementToSqlScalarExpressionVisitor Assert", "Unreachable");
throw new InvalidOperationException();
}

public SqlScalarExpression Visit(CosmosNull cosmosNull)
{
return SqlLiteralScalarExpression.Create(SqlNullLiteral.Create());
}

public SqlScalarExpression Visit(CosmosNumber cosmosNumber)
{
if (!(cosmosNumber is CosmosNumber64 cosmosNumber64))
{
throw new ArgumentException($"Unknown {nameof(CosmosNumber)} type: {cosmosNumber.GetType()}.");
}

return SqlLiteralScalarExpression.Create(SqlNumberLiteral.Create(cosmosNumber64.GetValue()));
}

public SqlScalarExpression Visit(CosmosObject cosmosObject)
{
List<SqlObjectProperty> properties = new List<SqlObjectProperty>();
foreach (KeyValuePair<string, CosmosElement> prop in cosmosObject)
{
SqlPropertyName name = SqlPropertyName.Create(prop.Key);
CosmosElement value = prop.Value;
SqlScalarExpression expression = value.Accept(this);
SqlObjectProperty property = SqlObjectProperty.Create(name, expression);
properties.Add(property);
}

return SqlObjectCreateScalarExpression.Create(properties.ToImmutableArray());
}

public SqlScalarExpression Visit(CosmosString cosmosString)
{
return SqlLiteralScalarExpression.Create(SqlStringLiteral.Create(cosmosString.Value));
}

public SqlScalarExpression Visit(CosmosUndefined cosmosUndefined)
{
return SqlLiteralScalarExpression.Create(SqlUndefinedLiteral.Create());
}
}
}
110 changes: 110 additions & 0 deletions Microsoft.Azure.Cosmos/src/Linq/DefaultCosmosLinqSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos.Linq
{
using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;
using Microsoft.Azure.Documents;
using Newtonsoft.Json;

internal class DefaultCosmosLinqSerializer : ICosmosLinqSerializer
{
public bool RequiresCustomSerialization(MemberExpression memberExpression, Type memberType)
{
// There are two ways to specify a custom attribute
// 1- by specifying the JsonConverterAttribute on a Class/Enum
// [JsonConverter(typeof(StringEnumConverter))]
// Enum MyEnum
// {
// ...
// }
//
// 2- by specifying the JsonConverterAttribute on a property
// class MyClass
// {
// [JsonConverter(typeof(StringEnumConverter))]
// public MyEnum MyEnum;
// }
//
// Newtonsoft gives high precedence to the attribute specified
// on a property over on a type (class/enum)
// so we check both attributes and apply the same precedence rules
// JsonConverterAttribute doesn't allow duplicates so it's safe to
// use FirstOrDefault()
CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute));
CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute));

return memberAttribute != null || typeAttribute != null;
}

public string Serialize(object value, MemberExpression memberExpression, Type memberType)
{
CustomAttributeData memberAttribute = memberExpression.Member.CustomAttributes.FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute));
CustomAttributeData typeAttribute = memberType.GetsCustomAttributes().FirstOrDefault(ca => ca.AttributeType == typeof(Newtonsoft.Json.JsonConverterAttribute));
CustomAttributeData converterAttribute = memberAttribute ?? typeAttribute;

Debug.Assert(converterAttribute.ConstructorArguments.Count > 0, $"{nameof(DefaultCosmosLinqSerializer)} Assert!", "At least one constructor argument exists.");
Type converterType = (Type)converterAttribute.ConstructorArguments[0].Value;

string serializedValue = converterType.GetConstructor(Type.EmptyTypes) != null
? JsonConvert.SerializeObject(value, (Newtonsoft.Json.JsonConverter)Activator.CreateInstance(converterType))
: JsonConvert.SerializeObject(value);

return serializedValue;
}

public string SerializeScalarExpression(ConstantExpression inputExpression)
{
return JsonConvert.SerializeObject(inputExpression.Value);
}

public string SerializeMemberName(MemberInfo memberInfo, CosmosLinqSerializerOptions linqSerializerOptions = null)
{
string memberName = null;

// Check if Newtonsoft JsonExtensionDataAttribute is present on the member, if so, return empty member name.
Newtonsoft.Json.JsonExtensionDataAttribute jsonExtensionDataAttribute = memberInfo.GetCustomAttribute<Newtonsoft.Json.JsonExtensionDataAttribute>(true);
if (jsonExtensionDataAttribute != null && jsonExtensionDataAttribute.ReadData)
{
return null;
}

// Json.Net honors JsonPropertyAttribute more than DataMemberAttribute
// So we check for JsonPropertyAttribute first.
JsonPropertyAttribute jsonPropertyAttribute = memberInfo.GetCustomAttribute<JsonPropertyAttribute>(true);
if (jsonPropertyAttribute != null && !string.IsNullOrEmpty(jsonPropertyAttribute.PropertyName))
{
memberName = jsonPropertyAttribute.PropertyName;
}
else
{
DataContractAttribute dataContractAttribute = memberInfo.DeclaringType.GetCustomAttribute<DataContractAttribute>(true);
if (dataContractAttribute != null)
{
DataMemberAttribute dataMemberAttribute = memberInfo.GetCustomAttribute<DataMemberAttribute>(true);
if (dataMemberAttribute != null && !string.IsNullOrEmpty(dataMemberAttribute.Name))
{
memberName = dataMemberAttribute.Name;
}
}
}

if (memberName == null)
{
memberName = memberInfo.Name;
}

if (linqSerializerOptions != null)
{
memberName = CosmosSerializationUtil.GetStringWithPropertyNamingPolicy(linqSerializerOptions, memberName);
}

return memberName;
}
}
}
Loading

0 comments on commit d287f08

Please sign in to comment.