Skip to content

Commit

Permalink
Sqlite: Support translation of ToString
Browse files Browse the repository at this point in the history
  • Loading branch information
ralmsdeveloper committed Feb 22, 2020
1 parent 2524a19 commit 74379d6
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public SqliteMethodCallTranslatorProvider([NotNull] RelationalMethodCallTranslat
new SqliteByteArrayMethodTranslator(sqlExpressionFactory),
new SqliteDateTimeAddTranslator(sqlExpressionFactory),
new SqliteMathTranslator(sqlExpressionFactory),
new SqliteStringMethodTranslator(sqlExpressionFactory)
new SqliteObjectToStringTranslator(sqlExpressionFactory),
new SqliteStringMethodTranslator(sqlExpressionFactory),
});
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal
{
public class SqliteObjectToStringTranslator : IMethodCallTranslator
{
private static readonly HashSet<Type> _typeMapping = new HashSet<Type>
{
typeof(int),
typeof(long),
typeof(Guid),
typeof(byte),
typeof(byte[]),
typeof(double),
typeof(float),
typeof(char),
typeof(short),
typeof(TimeSpan),
typeof(uint),
typeof(ushort),
typeof(sbyte),
};

private readonly ISqlExpressionFactory _sqlExpressionFactory;

public SqliteObjectToStringTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
=> _sqlExpressionFactory = sqlExpressionFactory;

public virtual SqlExpression Translate(SqlExpression instance, MethodInfo method, IReadOnlyList<SqlExpression> arguments)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));

return method.Name == nameof(ToString)
&& arguments.Count == 0
&& instance != null
&& _typeMapping.Contains(instance.Type.UnwrapNullableType())
? _sqlExpressionFactory.Convert(instance, typeof(string))
: null;
}
}
}
77 changes: 77 additions & 0 deletions test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,63 @@ public virtual void Can_perform_query_with_ansi_strings_test()
}
}

[ConditionalFact]
public virtual void Can_insert_and_read_using_convert_object_to_string_data_type()
{
using var context = CreateContext();
var objectToStringDataType = new ObjectToStringDataType
{
Id = 1,
TestInt16 = -1234,
TestInt32 = -123456789,
TestInt64 = -1234567890123456789L,
TestGuid = Guid.NewGuid(),
TestTimeSpan = new TimeSpan(0, 10, 9, 8, 7),
TestDouble = -1.234,
TestFloat = -1.24f,
TestByte = 255,
TestByteArray = new byte[] { 0, 1, 3, 4 },
TestUnsignedInt16 = 1234,
TestUnsignedInt32 = 1234565789U,
TestCharacter = 'a',
TestSignedByte = -128,
};

var set = context.Set<ObjectToStringDataType>();
set.Add(objectToStringDataType);

Assert.Equal(1, context.SaveChanges());

var data = set.Select(p => new
{
Id = p.Id.ToString(),
TestInt16 = p.TestInt16.ToString(),
TestInt32 = p.TestInt32.ToString(),
TestInt64 = p.TestInt64.ToString(),
TestTimeSpan = p.TestTimeSpan.ToString(),
TestByte = p.TestByte.ToString(),
TestDouble = p.TestDouble.ToString(),
TestFloat = p.TestFloat.ToString(),
TestUnsignedInt16 = p.TestUnsignedInt16.ToString(),
TestUnsignedInt32 = p.TestUnsignedInt32.ToString(),
TestCharacter = p.TestCharacter.ToString(),
TestSignedByte = p.TestSignedByte.ToString(),
}).Single();

Assert.Equal(objectToStringDataType.Id.ToString(), data.Id);
Assert.Equal(objectToStringDataType.TestInt16.ToString(), data.TestInt16);
Assert.Equal(objectToStringDataType.TestInt32.ToString(), data.TestInt32);
Assert.Equal(objectToStringDataType.TestInt64.ToString(), data.TestInt64);
Assert.Equal(objectToStringDataType.TestTimeSpan.ToString(), data.TestTimeSpan);
Assert.Equal(objectToStringDataType.TestByte.ToString(), data.TestByte);
Assert.Equal(objectToStringDataType.TestDouble.ToString(), data.TestDouble);
Assert.Equal(objectToStringDataType.TestFloat.ToString(), data.TestFloat);
Assert.Equal(objectToStringDataType.TestUnsignedInt16.ToString(), data.TestUnsignedInt16);
Assert.Equal(objectToStringDataType.TestUnsignedInt32.ToString(), data.TestUnsignedInt32);
Assert.Equal(objectToStringDataType.TestCharacter.ToString(), data.TestCharacter);
Assert.Equal(objectToStringDataType.TestSignedByte.ToString(), data.TestSignedByte);
}

[ConditionalFact]
public virtual void Can_query_using_any_data_type()
{
Expand Down Expand Up @@ -2250,6 +2307,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
AnimalId = 1,
Method = IdentificationMethod.EarTag
});

modelBuilder.Entity<ObjectToStringDataType>().Property(p => p.Id).ValueGeneratedNever();
}

protected static void MakeRequired<TEntity>(ModelBuilder modelBuilder)
Expand Down Expand Up @@ -3049,5 +3108,23 @@ protected enum IdentificationMethod
EarTag,
Rfid
}

protected class ObjectToStringDataType
{
public int Id { get; set; }
public short TestInt16 { get; set; }
public int TestInt32 { get; set; }
public long TestInt64 { get; set; }
public Guid TestGuid { get; set; }
public TimeSpan TestTimeSpan { get; set; }
public double TestDouble { get; set; }
public float TestFloat { get; set; }
public byte TestByte { get; set; }
public byte[] TestByteArray { get; set; }
public ushort TestUnsignedInt16 { get; set; }
public uint TestUnsignedInt32 { get; set; }
public char TestCharacter { get; set; }
public sbyte TestSignedByte { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2472,7 +2472,7 @@ public virtual void Columns_have_expected_data_types()
{
var actual = QueryForColumnTypes(
CreateContext(),
nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes));
nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes), nameof(ObjectToStringDataType));

const string expected = @"Animal.Id ---> [int] [Precision = 10 Scale = 0]
AnimalDetails.AnimalId ---> [nullable int] [Precision = 10 Scale = 0]
Expand Down Expand Up @@ -3016,6 +3016,11 @@ public static string QueryForColumnTypes(DbContext context, params string[] tabl
return actual;
}

// Disabled: Queries return Upper-case Guid strings
public override void Can_insert_and_read_using_convert_object_to_string_data_type()
{
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public virtual void Columns_have_expected_data_types()
{
var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(
CreateContext(),
nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes));
nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes), nameof(ObjectToStringDataType));

const string expected = @"Animal.Id ---> [int] [Precision = 10 Scale = 0]
AnimalDetails.AnimalId ---> [nullable int] [Precision = 10 Scale = 0]
Expand Down Expand Up @@ -164,6 +164,11 @@ public virtual void Columns_have_expected_data_types()
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
}

// Disabled: Queries return Upper-case Guid strings
public override void Can_insert_and_read_using_convert_object_to_string_data_type()
{
}

public class ConvertToProviderTypesSqlServerFixture : ConvertToProviderTypesFixtureBase
{
public override bool StrictEquality => true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public virtual void Columns_have_expected_data_types()
{
var actual = BuiltInDataTypesSqlServerTest.QueryForColumnTypes(
CreateContext(),
nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes));
nameof(ObjectBackedDataTypes), nameof(NullableBackedDataTypes), nameof(NonNullableBackedDataTypes), nameof(ObjectToStringDataType));

const string expected = @"Animal.Id ---> [int] [Precision = 10 Scale = 0]
AnimalDetails.AnimalId ---> [nullable int] [Precision = 10 Scale = 0]
Expand Down Expand Up @@ -255,6 +255,11 @@ public override void Where_bool_gets_converted_to_equality_when_value_conversion
WHERE [b].[Discriminator] IN (N'Blog', N'RssBlog') AND ([b].[IndexerVisible] <> N'Aye')");
}

// Disabled: Queries return Upper-case Guid strings
public override void Can_insert_and_read_using_convert_object_to_string_data_type()
{
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public virtual void Columns_have_expected_data_types()
nameof(ObjectBackedDataTypes),
nameof(NullableBackedDataTypes),
nameof(NonNullableBackedDataTypes),
nameof(AnimalDetails));
nameof(AnimalDetails),
nameof(ObjectToStringDataType));

const string expected = @"Animal.Id ---> [varbinary] [MaxLength = 4]
AnimalIdentification.AnimalId ---> [varbinary] [MaxLength = 4]
Expand Down Expand Up @@ -178,6 +179,11 @@ public override void Can_read_back_bool_mapped_as_int_through_navigation()
// Column is mapped as int rather than byte[]
}

public override void Can_insert_and_read_using_convert_object_to_string_data_type()
{
// The query needs to generate TOP(1)
}

public class EverythingIsBytesSqlServerFixture : BuiltInDataTypesFixtureBase
{
public override bool StrictEquality => true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public virtual void Columns_have_expected_data_types()
nameof(ObjectBackedDataTypes),
nameof(NullableBackedDataTypes),
nameof(NonNullableBackedDataTypes),
nameof(AnimalDetails));
nameof(AnimalDetails),
nameof(ObjectToStringDataType));

const string expected = @"Animal.Id ---> [nvarchar] [MaxLength = 64]
AnimalIdentification.AnimalId ---> [nvarchar] [MaxLength = 64]
Expand Down Expand Up @@ -179,6 +180,11 @@ public override void Can_read_back_bool_mapped_as_int_through_navigation()
// Column is mapped as int rather than string
}

public override void Can_insert_and_read_using_convert_object_to_string_data_type()
{
// The query needs to generate TOP(1)
}

public class EverythingIsStringsSqlServerFixture : BuiltInDataTypesFixtureBase
{
public override bool StrictEquality => true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,11 @@ private void AssertTranslationFailed(Action testCode)
private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

//// Disabled: Queries return Upper-case Guid strings
//public override void Can_insert_and_read_using_convert_object_to_string_data_type()
//{
//}

public class BuiltInDataTypesSqliteFixture : BuiltInDataTypesFixtureBase
{
public override bool StrictEquality => false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.EntityFrameworkCore
Expand All @@ -17,6 +18,11 @@ public ConvertToProviderTypesSqliteTest(ConvertToProviderTypesSqliteFixture fixt
//fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

//// Disabled: Queries return Upper-case Guid strings
//public override void Can_insert_and_read_using_convert_object_to_string_data_type()
//{
//}

public class ConvertToProviderTypesSqliteFixture : ConvertToProviderTypesFixtureBase
{
public override bool StrictEquality => false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public override void Can_insert_and_read_back_with_case_insensitive_string_key()
{
}

//// Disabled: Queries return Upper-case Guid strings
//public override void Can_insert_and_read_using_convert_object_to_string_data_type()
//{
// base.Can_insert_and_read_using_convert_object_to_string_data_type();
//}

[ConditionalFact]
public override void Value_conversion_is_appropriately_used_for_join_condition()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,15 @@ public NorthwindMiscellaneousQuerySqliteTest(NorthwindQuerySqliteFixture<NoopMod
//Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}

public override Task Query_expression_with_to_string_and_contains(bool async)
=> AssertTranslationFailed(() => base.Query_expression_with_to_string_and_contains(async));
public override async Task Query_expression_with_to_string_and_contains(bool async)
{
await base.Query_expression_with_to_string_and_contains(async);

AssertSql(
@"SELECT ""o"".""CustomerID""
FROM ""Orders"" AS ""o""
WHERE ""o"".""OrderDate"" IS NOT NULL AND (('10' = '') OR (instr(CAST(""o"".""EmployeeID"" AS TEXT), '10') > 0))");
}

public override async Task Take_Skip(bool async)
{
Expand Down Expand Up @@ -235,6 +242,26 @@ LIMIT @__p_0
) AS ""t""");
}

public override async Task Select_expression_long_to_string(bool async)
{
await base.Select_expression_long_to_string(async);

AssertSql(
@"SELECT CAST(CAST(""o"".""OrderID"" AS INTEGER) AS TEXT) AS ""ShipName""
FROM ""Orders"" AS ""o""
WHERE ""o"".""OrderDate"" IS NOT NULL");
}

public override async Task Select_expression_int_to_string(bool async)
{
await base.Select_expression_int_to_string(async);

AssertSql(
@"SELECT CAST(""o"".""OrderID"" AS TEXT) AS ""ShipName""
FROM ""Orders"" AS ""o""
WHERE ""o"".""OrderDate"" IS NOT NULL");
}

public override Task Complex_nested_query_doesnt_try_binding_to_grandparent_when_parent_returns_complex_result(bool async)
=> null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,16 @@ public override async Task Decimal_cast_to_double_works(bool async)
WHERE CAST(""p"".""UnitPrice"" AS REAL) > 100.0");
}

[ConditionalTheory(Skip = "Issue#17223")]
public override Task Like_with_non_string_column_using_ToString(bool async)
=> base.Like_with_non_string_column_using_ToString(async);
public override async Task Like_with_non_string_column_using_ToString(bool async)
{
await base.Like_with_non_string_column_using_ToString(async);

AssertSql(
@"SELECT ""o"".""OrderID"", ""o"".""CustomerID"", ""o"".""EmployeeID"", ""o"".""OrderDate""
FROM ""Orders"" AS ""o""
WHERE CAST(""o"".""OrderID"" AS TEXT) LIKE '%20%'");
}


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

0 comments on commit 74379d6

Please sign in to comment.