Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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