Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global Filters with Joins cause stack overflow when query cache is enabled #4496

Open
joonatanu-softwerk opened this issue May 6, 2024 · 0 comments

Comments

@joonatanu-softwerk
Copy link

joonatanu-softwerk commented May 6, 2024

Describe your issue

When query filter is defined through FluentMappingBuilder using the Func<IQueryable<T>, IDataContext, IQueryable<T>> type definition and it contains any joins, "Stack overflow" error occurs.

Provided example is using SQLite, I have also confirmed this behavior using .NET 6 and MSSQL server.

EDIT: When query cache is disabled, everything works as expected.

For background: Usage of query filters is necessary, as we need certain conditions to apply always, including when we are traversing through entity associations in our queries. (Either directly or by AutoMapper's expression-based projections)

(NB! We have also developed our own multi-filter-to-single-filter aggregator as Linq2Db is currently not supporting multiple simultaneous query filters. I should maybe create a feature-request on this?)

Exception message: Stack overflow.
Stack trace:
   at System.RuntimeMethodHandle.GetMethodInstantiationPublic(System.IRuntimeMethodInfo)
   at System.Reflection.RuntimeMethodInfo.get_ContainsGenericParameters()
   at System.Linq.Expressions.Expression.ValidateMethodInfo(System.Reflection.MethodInfo, System.String)
   at System.Linq.Expressions.Expression.ValidateMethodAndGetParameters(System.Linq.Expressions.Expression, System.Reflection.MethodInfo)
   at System.Linq.Expressions.Expression.Call(System.Linq.Expressions.Expression, System.Reflection.MethodInfo, System.Linq.Expressions.Expression)
   at System.Linq.Expressions.Expression.Call(System.Linq.Expressions.Expression, System.Reflection.MethodInfo, System.Collections.Generic.IEnumerable`1<System.Linq.Expressions.Expression>)
   at LinqToDB.Expressions.TransformVisitor`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TransformX(System.Linq.Expressions.MethodCallExpression)
   at LinqToDB.Expressions.TransformVisitor`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Transform(System.Linq.Expressions.Expression)
   at LinqToDB.Expressions.TransformVisitor`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Transform[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IList`1<System.__Canon>)
   at LinqToDB.Expressions.TransformVisitor`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TransformX(System.Linq.Expressions.MethodCallExpression)
   at LinqToDB.Expressions.TransformVisitor`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Transform(System.Linq.Expressions.Expression)
   at LinqToDB.Expressions.TransformVisitor`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Transform[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Collections.Generic.IList`1<System.__Canon>)
   at LinqToDB.Expressions.TransformVisitor`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TransformX(System.Linq.Expressions.MethodCallExpression)
   at LinqToDB.Expressions.TransformVisitor`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Transform(System.Linq.Expressions.Expression)
   at LinqToDB.Expressions.ExpressionExtensions.Transform[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Linq.Expressions.Expression, System.__Canon, System.Func`3<System.__Canon,System.Linq.Expressions.Expression,System.Linq.Expressions.Expression>)
   at LinqToDB.Linq.Builder.ExpressionBuilder.CorrectDataConnectionReference(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression)
   at LinqToDB.Linq.Builder.TableBuilder+<>c.<ApplyQueryFilters>b__7_0(System.ValueTuple`2<System.Delegate,System.Linq.IQueryable>, System.Reflection.MemberInfo, LinqToDB.IDataContext)
   at LinqToDB.Linq.QueryableMemberAccessor`1[[System.ValueTuple`2[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Execute(System.Reflection.MemberInfo, LinqToDB.IDataContext)
   at LinqToDB.Expressions.EqualsToVisitor.CompareMemberExpression(System.Reflection.MemberInfo, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsToX(System.Linq.Expressions.MethodCallExpression, System.Linq.Expressions.MethodCallExpression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsTo(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.DefaultCompareArguments(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsToX(System.Linq.Expressions.MethodCallExpression, System.Linq.Expressions.MethodCallExpression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsTo(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.DefaultCompareArguments(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsToX(System.Linq.Expressions.MethodCallExpression, System.Linq.Expressions.MethodCallExpression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsTo(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)

... (identical rows for hundreds of times) ...

   at LinqToDB.Expressions.EqualsToVisitor.DefaultCompareArguments(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsToX(System.Linq.Expressions.MethodCallExpression, System.Linq.Expressions.MethodCallExpression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsTo(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.CompareMemberExpression(System.Reflection.MemberInfo, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsToX(System.Linq.Expressions.MethodCallExpression, System.Linq.Expressions.MethodCallExpression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsTo(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.DefaultCompareArguments(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsToX(System.Linq.Expressions.MethodCallExpression, System.Linq.Expressions.MethodCallExpression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsTo(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, EqualsToInfo)
   at LinqToDB.Expressions.EqualsToVisitor.EqualsTo(System.Linq.Expressions.Expression, System.Linq.Expressions.Expression, LinqToDB.IDataContext, System.Collections.Generic.IReadOnlyDictionary`2<System.Linq.Expressions.Expression,LinqToDB.Linq.QueryableAccessor>, System.Collections.Generic.IReadOnlyDictionary`2<System.Reflection.MemberInfo,LinqToDB.Linq.QueryableMemberAccessor>, System.Collections.Generic.IReadOnlyDictionary`2<System.Linq.Expressions.Expression,System.Linq.Expressions.Expression>, Boolean)
   at LinqToDB.Linq.Query.Compare(LinqToDB.IDataContext, System.Linq.Expressions.Expression)
   at LinqToDB.Linq.Query`1+QueryCache+QueryCacheEntry[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Compare(LinqToDB.IDataContext, System.Linq.Expressions.Expression, LinqToDB.Linq.QueryFlags)
   at LinqToDB.Linq.Query`1+QueryCache[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Find(LinqToDB.IDataContext, System.Linq.Expressions.Expression, LinqToDB.Linq.QueryFlags, LinqToDB.DataOptions)
   at LinqToDB.Linq.Query`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].GetQuery(LinqToDB.IDataContext, System.Linq.Expressions.Expression ByRef, Boolean ByRef)
   at LinqToDB.Linq.ExpressionQuery`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].GetQuery(System.Linq.Expressions.Expression ByRef, Boolean, Boolean ByRef)
   at LinqToDB.Linq.ExpressionQuery`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].get_SqlText()
   at LinqToDB.Linq.ExpressionQueryImpl`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ToString()
   at Program.<Main>$(System.String[])

Steps to reproduce

Create new console application. Install nugets linq2db (5.4.1) and System.Data.SQLite.Core (1.0.118) and then paste this code into Program.cs:

using LinqToDB;
using LinqToDB.Data;
using LinqToDB.Mapping;

using var dbCtx = new MyDbCtx(new DataOptions(new ConnectionOptions()
{
    ConnectionString = "Data Source=:memory:;Version=3;New=True;",
    ProviderName = ProviderName.SQLite
}));

dbCtx.CreateTable<User>();
dbCtx.CreateTable<Post>();


var query = dbCtx.GetTable<Post>()
    .Where(x => x.Content.StartsWith("A"));

var result = query.ToList();

Console.ReadLine(); // Add break-point here and try to look at query.SqlText


public class MyDbCtx : DataConnection
{
    public MyDbCtx(DataOptions options) : base(options)
    {
        var builder = new FluentMappingBuilder(MappingSchema.Default);

        builder.Entity<Post>().HasQueryFilter((q, ctx) => q
            .InnerJoin(
                ctx.GetTable<User>(),
                (p, u) => p.UserId == u.Id && u.Name.StartsWith("B"),
                (p, u) => p
            )
            .Distinct()
        );

        builder.Build();

        AddMappingSchema(builder.MappingSchema);
    }
}

public record User
{
    public Guid Id { get; set; }

    public string Name { get; set; } = null!;
}

public record Post
{
    public Guid Id { get; set; }

    public Guid UserId { get; set; }

    public string Content { get; set; } = null!;
}

Environment details

Linq To DB version: 5.4.1

Database (with version): SQLite 1.0.118

ADO.NET Provider (with version): System.Data.SQLite.Core 1.0.118

Operating system: Windows 11

.NET Version: 8.0

@joonatanu-softwerk joonatanu-softwerk changed the title Global Filters with Joins cause stack overflow Global Filters with Joins cause stack overflow when query cache is enabled May 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant