Skip to content

Commit

Permalink
Fix #27 by casting literal strings in projection lists + explicit cas…
Browse files Browse the repository at this point in the history
…t function + tests.
  • Loading branch information
rwasef1830 committed Jul 8, 2016
1 parent 7c5a096 commit e1689b9
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/EntityFramework6.Npgsql/EntityFramework6.Npgsql.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
<Compile Include="NpgsqlServices.cs" />
<Compile Include="NpgsqlProviderManifest.cs" />
<Compile Include="NpgsqlTextFunctions.cs" />
<Compile Include="NpgsqlTypeFunctions.cs" />
<Compile Include="NpgsqlWeightLabel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Spatial\PostgisDataReader.cs" />
Expand Down
8 changes: 4 additions & 4 deletions src/EntityFramework6.Npgsql/NpgsqlProviderManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -351,15 +351,15 @@ public override string EscapeLikeArgument([NotNull] string argument)
public override bool SupportsInExpression() => true;

public override ReadOnlyCollection<EdmFunction> GetStoreFunctions()
=> typeof(NpgsqlTextFunctions).GetTypeInfo()
.GetMethods(BindingFlags.Public | BindingFlags.Static)
=> new[] { typeof(NpgsqlTextFunctions).GetTypeInfo(), typeof(NpgsqlTypeFunctions) }
.SelectMany(x => x.GetMethods(BindingFlags.Public | BindingFlags.Static))
.Select(x => new { Method = x, DbFunction = x.GetCustomAttribute<DbFunctionAttribute>() })
.Where(x => x.DbFunction != null)
.Select(x => CreateFullTextEdmFunction(x.Method, x.DbFunction))
.Select(x => CreateComposableEdmFunction(x.Method, x.DbFunction))
.ToList()
.AsReadOnly();

static EdmFunction CreateFullTextEdmFunction([NotNull] MethodInfo method, [NotNull] DbFunctionAttribute dbFunctionInfo)
static EdmFunction CreateComposableEdmFunction([NotNull] MethodInfo method, [NotNull] DbFunctionAttribute dbFunctionInfo)
{
if (method == null)
throw new ArgumentNullException(nameof(method));
Expand Down
20 changes: 20 additions & 0 deletions src/EntityFramework6.Npgsql/NpgsqlTypeFunctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Data.Entity;

namespace Npgsql
{
/// <summary>
/// Use this class in LINQ queries to emit type manipulation SQL fragments.
/// </summary>
public static class NpgsqlTypeFunctions
{
/// <summary>
/// Emits an explicit cast for unknown types sent as strings to their correct postgresql type.
/// </summary>
[DbFunction("Npgsql", "cast")]
public static string Cast(string unknownTypeValue, string postgresTypeName)
{
throw new NotSupportedException();
}
}
}
20 changes: 19 additions & 1 deletion src/EntityFramework6.Npgsql/SqlGenerators/SqlBaseGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,14 @@ PendingProjectsNode VisitInputWithBinding(DbExpression expression, string bindin
for (var i = 0; i < rowType.Properties.Count && i < projection.Arguments.Count; ++i)
{
var prop = rowType.Properties[i];
input.Projection.Arguments.Add(new ColumnExpression(projection.Arguments[i].Accept(this), prop.Name, prop.TypeUsage));
var argument = projection.Arguments[i].Accept(this);
var constantArgument = projection.Arguments[i] as DbConstantExpression;
if (constantArgument != null && constantArgument.Value is string)
{
argument = new CastExpression(argument, "varchar");
}

input.Projection.Arguments.Add(new ColumnExpression(argument, prop.Name, prop.TypeUsage));
}

if (enterScope) LeaveExpression(child);
Expand Down Expand Up @@ -1150,6 +1157,17 @@ VisitedExpression VisitFunction(EdmFunction function, IList<DbExpression> args,
{
return VisitMatchRegex(function, args, resultType);
}
else if (functionName == "cast")
{
if (args.Count != 2)
throw new ArgumentException("Invalid number of arguments. Expected 2.", "args");

var typeNameExpression = args[1] as DbConstantExpression;
if (typeNameExpression == null)
throw new NotSupportedException("cast type name argument must be a constant expression.");

return new CastExpression(args[0].Accept(this), typeNameExpression.Value.ToString());
}
}

var customFuncCall = new FunctionExpression(
Expand Down
69 changes: 69 additions & 0 deletions test/EntityFramework6.Npgsql.Tests/EntityFrameworkBasicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -650,5 +650,74 @@ public void TestScalarValuedStoredFunctions_with_null_StoreFunctionName()
Assert.That(echo, Is.EqualTo(1337));
}
}

[Test]
public void TestCastFunction()
{
using (var context = new BloggingContext(ConnectionString))
{
context.Database.Log = Console.Out.WriteLine;

var varbitVal = "10011";

var blog = new Blog
{
Name = "_",
Posts = new List<Post>
{
new Post
{
Content = "Some post content",
Rating = 1,
Title = "Some post Title",
VarbitColumn = varbitVal
}
}
};
context.Blogs.Add(blog);
context.SaveChanges();

Assert.IsTrue(
context.Posts.Select(
p => NpgsqlTypeFunctions.Cast(p.VarbitColumn, "varbit") == varbitVal).First());

Assert.IsTrue(
context.Posts.Select(
p => NpgsqlTypeFunctions.Cast(p.VarbitColumn, "varbit") == "10011").First());
}
}

[Test]
public void Test_issue_27_select_ef_generated_literals_from_inner_select()
{
using (var context = new BloggingContext(ConnectionString))
{
context.Database.Log = Console.Out.WriteLine;

var blog = new Blog { Name = "Hello" };
context.Users.Add(new Administrator { Blogs = new List<Blog> { blog } });
context.Users.Add(new Editor());
context.SaveChanges();

var administrator = context.Users
.Where(x => x is Administrator) // Removing this changes the query to using a UNION which doesn't fail.
.Select(
x => new
{
// causes entity framework to emit a literal discriminator
Computed = x is Administrator
? "I administrate"
: x is Editor
? "I edit"
: "Unknown",
// causes an inner select to be emitted thus showing the issue
HasBlog = x.Blogs.Any()
})
.First();

Assert.That(administrator.Computed, Is.EqualTo("I administrate"));
Assert.That(administrator.HasBlog, Is.True);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ public class NoColumnsEntity
public int Id { get; set; }
}

[Table("Users")]
public abstract class User
{
public int Id { get; set; }

public IList<Blog> Blogs { get; set; }
}

[Table("Editors")]
public class Editor : User { }

[Table("Administrators")]
public class Administrator : User { }

public class BloggingContext : DbContext
{
public BloggingContext(string connection)
Expand All @@ -114,6 +128,9 @@ public BloggingContext(string connection)
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<NoColumnsEntity> NoColumnsEntities { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Editor> Editors { get; set; }
public DbSet<Administrator> Administrators { get; set; }

[DbFunction("BloggingContext", "ClrStoredAddFunction")]
public static int StoredAddFunction(int val1, int val2)
Expand All @@ -135,6 +152,9 @@ private static DbCompiledModel CreateModel(NpgsqlConnection connection)
dbModelBuilder.Entity<Blog>();
dbModelBuilder.Entity<Post>();
dbModelBuilder.Entity<NoColumnsEntity>();
dbModelBuilder.Entity<User>();
dbModelBuilder.Entity<Editor>();
dbModelBuilder.Entity<Administrator>();

// Import function
var dbModel = dbModelBuilder.Build(connection);
Expand Down

0 comments on commit e1689b9

Please sign in to comment.