Skip to content

Commit

Permalink
Query: Correct translation of SqlServer ToString
Browse files Browse the repository at this point in the history
Resolves #20433
  • Loading branch information
smitpatel committed Mar 31, 2020
1 parent 42c912c commit 5fff1f6
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 31 deletions.
25 changes: 17 additions & 8 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,6 @@ dotnet_naming_rule.type_parameter_naming.symbols = type_parameter_symbol
dotnet_naming_rule.type_parameter_naming.style = type_parameter_style
dotnet_naming_rule.type_parameter_naming.severity = suggestion

# Private Fields
dotnet_naming_symbols.private_field_symbol.applicable_kinds = field
dotnet_naming_symbols.private_field_symbol.applicable_accessibilities = private

dotnet_naming_rule.private_field_naming.symbols = private_field_symbol
dotnet_naming_rule.private_field_naming.style = _camelCase
dotnet_naming_rule.private_field_naming.severity = suggestion

# Visible Fields
dotnet_naming_symbols.public_field_symbol.applicable_kinds = field
dotnet_naming_symbols.public_field_symbol.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
Expand All @@ -230,6 +222,23 @@ dotnet_naming_rule.public_field_naming.symbols = public_field_symbol
dotnet_naming_rule.public_field_naming.style = pascal_case_style
dotnet_naming_rule.public_field_naming.severity = suggestion

# Private constant Fields
dotnet_naming_symbols.const_field_symbol.applicable_kinds = field
dotnet_naming_symbols.const_field_symbol.applicable_accessibilities = private
dotnet_naming_symbols.const_field_symbol.required_modifiers = const

dotnet_naming_rule.const_field_naming.symbols = const_field_symbol
dotnet_naming_rule.const_field_naming.style = pascal_case_style
dotnet_naming_rule.const_field_naming.severity = suggestion

# Private Fields
dotnet_naming_symbols.private_field_symbol.applicable_kinds = field
dotnet_naming_symbols.private_field_symbol.applicable_accessibilities = private

dotnet_naming_rule.private_field_naming.symbols = private_field_symbol
dotnet_naming_rule.private_field_naming.style = _camelCase
dotnet_naming_rule.private_field_naming.severity = suggestion

# Parameters
dotnet_naming_symbols.parameter_symbol.applicable_kinds = parameter
dotnet_naming_symbols.parameter_symbol.applicable_accessibilities = *
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ public class SqlServerObjectToStringTranslator : IMethodCallTranslator
private static readonly Dictionary<Type, string> _typeMapping
= new Dictionary<Type, string>
{
{ typeof(int), "VARCHAR(11)" },
{ typeof(long), "VARCHAR(20)" },
{ typeof(DateTime), $"VARCHAR({DefaultLength})" },
{ typeof(Guid), "VARCHAR(36)" },
{ typeof(sbyte), "VARCHAR(4)" },
{ typeof(byte), "VARCHAR(3)" },
{ typeof(byte[]), $"VARCHAR({DefaultLength})" },
{ typeof(double), $"VARCHAR({DefaultLength})" },
{ typeof(DateTimeOffset), $"VARCHAR({DefaultLength})" },
{ typeof(char), "VARCHAR(1)" },
{ typeof(short), "VARCHAR(6)" },
{ typeof(ushort), "VARCHAR(5)" },
{ typeof(int), "VARCHAR(11)" },
{ typeof(uint), "VARCHAR(10)" },
{ typeof(long), "VARCHAR(20)" },
{ typeof(ulong), "VARCHAR(20)" },
{ typeof(float), $"VARCHAR({DefaultLength})" },
{ typeof(double), $"VARCHAR({DefaultLength})" },
{ typeof(decimal), $"VARCHAR({DefaultLength})" },
{ typeof(char), "VARCHAR(1)" },
{ typeof(DateTime), $"VARCHAR({DefaultLength})" },
{ typeof(DateTimeOffset), $"VARCHAR({DefaultLength})" },
{ typeof(TimeSpan), $"VARCHAR({DefaultLength})" },
{ typeof(uint), "VARCHAR(10)" },
{ typeof(ushort), "VARCHAR(5)" },
{ typeof(ulong), "VARCHAR(19)" },
{ typeof(sbyte), "VARCHAR(4)" }
{ typeof(Guid), "VARCHAR(36)" },
{ typeof(byte[]), $"VARCHAR({DefaultLength})" },
};

private readonly ISqlExpressionFactory _sqlExpressionFactory;
Expand All @@ -52,16 +52,14 @@ public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method
return method.Name == nameof(ToString)
&& arguments.Count == 0
&& instance != null
&& _typeMapping.TryGetValue(
instance.Type.UnwrapNullableType(),
out var storeType)
? _sqlExpressionFactory.Function(
"CONVERT",
new[] { _sqlExpressionFactory.Fragment(storeType), instance },
nullable: true,
argumentsPropagateNullability: new bool[] { false, true },
typeof(string))
: null;
&& _typeMapping.TryGetValue(instance.Type.UnwrapNullableType(), out var storeType)
? _sqlExpressionFactory.Function(
"CONVERT",
new[] { _sqlExpressionFactory.Fragment(storeType), instance },
nullable: true,
argumentsPropagateNullability: new bool[] { false, true },
typeof(string))
: null;
}
}
}
13 changes: 13 additions & 0 deletions test/EFCore.Cosmos.FunctionalTests/BuiltInDataTypesCosmosTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ public override void Can_read_back_bool_mapped_as_int_through_navigation()
base.Can_read_back_bool_mapped_as_int_through_navigation();
}

[ConditionalFact(Skip = "Issue #20543")]
public override void Object_to_string_conversion()
{
base.Object_to_string_conversion();

AssertSql(@" ");
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

public class BuiltInDataTypesCosmosFixture : BuiltInDataTypesFixtureBase
{
protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance;
Expand All @@ -113,6 +124,8 @@ public class BuiltInDataTypesCosmosFixture : BuiltInDataTypesFixtureBase

public override bool SupportsDecimalComparisons => true;

public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory;

public override DateTime DefaultDateTime => new DateTime();

protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ FROM root c
WHERE (c[""Discriminator""] IN (""Blog"", ""RssBlog"") AND NOT((c[""IndexerVisible""] = ""Aye"")))");
}

[ConditionalFact(Skip = "Issue #20543")]
public override void Object_to_string_conversion()
{
base.Object_to_string_conversion();

AssertSql(@" ");
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

Expand Down
63 changes: 63 additions & 0 deletions test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1994,6 +1994,69 @@ public virtual void Can_read_back_bool_mapped_as_int_through_navigation()
Assert.True(result.BoolField);
}

[ConditionalFact]
public virtual void Object_to_string_conversion()
{
using var context = CreateContext();
var expected = context.Set<BuiltInDataTypes>()
.Where(e => e.Id == 13)
.AsEnumerable()
.Select(b => new
{
Sbyte = b.TestSignedByte.ToString(),
Byte = b.TestByte.ToString(),
Short = b.TestInt16.ToString(),
Ushort = b.TestUnsignedInt16.ToString(),
Int = b.TestInt32.ToString(),
Uint = b.TestUnsignedInt32.ToString(),
Long = b.TestInt64.ToString(),
Ulong = b.TestUnsignedInt64.ToString(),
Float = b.TestSingle.ToString(),
Double = b.TestDouble.ToString(),
Decimal = b.TestDecimal.ToString(),
Char = b.TestCharacter.ToString(),
DateTime = b.TestDateTime.ToString(),
DateTimeOffset = b.TestDateTimeOffset.ToString(),
TimeSpan = b.TestTimeSpan.ToString(),
})
.First();

var query = context.Set<BuiltInDataTypes>()
.Where(e => e.Id == 13)
.Select(b => new
{
Sbyte = b.TestSignedByte.ToString(),
Byte = b.TestByte.ToString(),
Short = b.TestInt16.ToString(),
Ushort = b.TestUnsignedInt16.ToString(),
Int = b.TestInt32.ToString(),
Uint = b.TestUnsignedInt32.ToString(),
Long = b.TestInt64.ToString(),
Ulong = b.TestUnsignedInt64.ToString(),
Float = b.TestSingle.ToString(),
Double = b.TestDouble.ToString(),
Decimal = b.TestDecimal.ToString(),
Char = b.TestCharacter.ToString(),
DateTime = b.TestDateTime.ToString(),
DateTimeOffset = b.TestDateTimeOffset.ToString(),
TimeSpan = b.TestTimeSpan.ToString(),
})
.ToList();

var actual = Assert.Single(query);
Assert.Equal(expected.Sbyte, actual.Sbyte);
Assert.Equal(expected.Byte, actual.Byte);
Assert.Equal(expected.Short, actual.Short);
Assert.Equal(expected.Ushort, actual.Ushort);
Assert.Equal(expected.Int, actual.Int);
Assert.Equal(expected.Uint, actual.Uint);
Assert.Equal(expected.Long, actual.Long);
Assert.Equal(expected.Ulong, actual.Ulong);
Assert.Equal(expected.Decimal, actual.Decimal);
Assert.Equal(expected.Char, actual.Char);
Assert.Equal(expected.TimeSpan, actual.TimeSpan);
}

public abstract class BuiltInDataTypesFixtureBase : SharedStoreFixtureBase<PoolableDbContext>
{
protected override string StoreName { get; } = "BuiltInDataTypes";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,6 @@ public virtual void Collection_enum_as_string_Contains()
Assert.Throws<InvalidOperationException>(
() => context.Set<CollectionEnum>().Where(e => e.Roles.Contains(sameRole)).ToList())
.Message.Replace("\r", "").Replace("\n", ""));

}

protected class CollectionEnum
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2915,6 +2915,20 @@ public void Can_get_column_types_from_built_model()
}
}

public override void Object_to_string_conversion()
{
base.Object_to_string_conversion();

AssertSql(
@"SELECT [b].[Id], [b].[Enum16], [b].[Enum32], [b].[Enum64], [b].[Enum8], [b].[EnumS8], [b].[EnumU16], [b].[EnumU32], [b].[EnumU64], [b].[PartitionId], [b].[TestBoolean], [b].[TestByte], [b].[TestCharacter], [b].[TestDateTime], [b].[TestDateTimeOffset], [b].[TestDecimal], [b].[TestDouble], [b].[TestInt16], [b].[TestInt32], [b].[TestInt64], [b].[TestSignedByte], [b].[TestSingle], [b].[TestTimeSpan], [b].[TestUnsignedInt16], [b].[TestUnsignedInt32], [b].[TestUnsignedInt64]
FROM [BuiltInDataTypes] AS [b]
WHERE [b].[Id] = 13",
//
@"SELECT CONVERT(VARCHAR(4), [b].[TestSignedByte]) AS [Sbyte], CONVERT(VARCHAR(3), [b].[TestByte]) AS [Byte], CONVERT(VARCHAR(6), [b].[TestInt16]) AS [Short], CONVERT(VARCHAR(5), [b].[TestUnsignedInt16]) AS [Ushort], CONVERT(VARCHAR(11), [b].[TestInt32]) AS [Int], CONVERT(VARCHAR(10), [b].[TestUnsignedInt32]) AS [Uint], CONVERT(VARCHAR(20), [b].[TestInt64]) AS [Long], CONVERT(VARCHAR(20), [b].[TestUnsignedInt64]) AS [Ulong], CONVERT(VARCHAR(100), [b].[TestSingle]) AS [Float], CONVERT(VARCHAR(100), [b].[TestDouble]) AS [Double], CONVERT(VARCHAR(100), [b].[TestDecimal]) AS [Decimal], CONVERT(VARCHAR(1), [b].[TestCharacter]) AS [Char], CONVERT(VARCHAR(100), [b].[TestDateTime]) AS [DateTime], CONVERT(VARCHAR(100), [b].[TestDateTimeOffset]) AS [DateTimeOffset], CONVERT(VARCHAR(100), [b].[TestTimeSpan]) AS [TimeSpan]
FROM [BuiltInDataTypes] AS [b]
WHERE [b].[Id] = 13");
}

public static string QueryForColumnTypes(DbContext context, params string[] tablesToIgnore)
{
const string query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ public virtual void Columns_have_expected_data_types()
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
}

public override void Object_to_string_conversion()
{
// Return values are not string
}

public class ConvertToProviderTypesSqlServerFixture : ConvertToProviderTypesFixtureBase
{
public override bool StrictEquality => true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ FROM [Blog] AS [b]
WHERE [b].[IndexerVisible] <> N'Aye'");
}

public override void Object_to_string_conversion()
{
// Return values are not string
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ public override void Can_read_back_bool_mapped_as_int_through_navigation()
// Column is mapped as int rather than byte[]
}

public override void Object_to_string_conversion()
{
// Return values are string which byte[] cannot read
}

public class EverythingIsBytesSqlServerFixture : BuiltInDataTypesFixtureBase
{
public override bool StrictEquality => true;
Expand Down
14 changes: 14 additions & 0 deletions test/EFCore.Sqlite.FunctionalTests/BuiltInDataTypesSqliteTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,20 @@ public virtual void Cant_query_ThenBy_of_converted_types()
Assert.Equal(SqliteStrings.OrderByNotSupported("ulong"), ex.Message);
}

public override void Object_to_string_conversion()
{
base.Object_to_string_conversion();

AssertSql(
@"SELECT ""b"".""Id"", ""b"".""Enum16"", ""b"".""Enum32"", ""b"".""Enum64"", ""b"".""Enum8"", ""b"".""EnumS8"", ""b"".""EnumU16"", ""b"".""EnumU32"", ""b"".""EnumU64"", ""b"".""PartitionId"", ""b"".""TestBoolean"", ""b"".""TestByte"", ""b"".""TestCharacter"", ""b"".""TestDateTime"", ""b"".""TestDateTimeOffset"", ""b"".""TestDecimal"", ""b"".""TestDouble"", ""b"".""TestInt16"", ""b"".""TestInt32"", ""b"".""TestInt64"", ""b"".""TestSignedByte"", ""b"".""TestSingle"", ""b"".""TestTimeSpan"", ""b"".""TestUnsignedInt16"", ""b"".""TestUnsignedInt32"", ""b"".""TestUnsignedInt64""
FROM ""BuiltInDataTypes"" AS ""b""
WHERE ""b"".""Id"" = 13",
//
@"SELECT ""b"".""TestSignedByte"", ""b"".""TestByte"", ""b"".""TestInt16"", ""b"".""TestUnsignedInt16"", ""b"".""TestInt32"", ""b"".""TestUnsignedInt32"", ""b"".""TestInt64"", ""b"".""TestUnsignedInt64"", ""b"".""TestSingle"", ""b"".""TestDouble"", ""b"".""TestDecimal"", ""b"".""TestCharacter"", ""b"".""TestDateTime"", ""b"".""TestDateTimeOffset"", ""b"".""TestTimeSpan""
FROM ""BuiltInDataTypes"" AS ""b""
WHERE ""b"".""Id"" = 13");
}

private void AssertTranslationFailed(Action testCode)
=> Assert.Contains(
CoreStrings.TranslationFailed("").Substring(21),
Expand Down

0 comments on commit 5fff1f6

Please sign in to comment.