Summary
FbQuerySqlGenerator already knows that Firebird will not accept a bare source after LATERAL: VisitCrossApply and VisitOuterApply both special-case a TableExpression and emit (SELECT * FROM "T") AS "t". The same branch is missing for a TableValuedFunctionExpression, so a correlated queryable function is emitted as a bare call and the statement does not parse.
I think this is the cause of the 14 [NotSupportedOnFirebirdFact] skips in UdfDbFunctionFbTests (all the QF_*Correlated* / QF_*Apply* ones). The store supports the query; only the generated SQL is malformed.
Repro
EF Core 10.0.0, FirebirdSql.EntityFrameworkCore.Firebird 13.0.0, Firebird 5.0.3 embedded.
create procedure "PostsOf" (blogId int)
returns ("PostId" int, "Heading" varchar(450))
as
begin
for select "Id", "Heading" from "Posts" where "BlogId" = :blogId
into :"PostId", :"Heading" do
begin
suspend;
end
end
public IQueryable<PostRow> PostsOf(int blogId)
=> FromExpression(() => PostsOf(blogId));
// OnModelCreating
modelBuilder.Entity<PostRow>().HasNoKey().ToTable((string?)null);
modelBuilder.HasDbFunction(typeof(MyContext).GetMethod(nameof(PostsOf))!);
var rows = (from blog in context.Blogs
from post in context.PostsOf(blog.Id)
select new { blog.Id, post.Heading }).ToList();
Actual
SELECT "b"."Id", "p"."Heading"
FROM "Blogs" AS "b"
JOIN LATERAL "PostsOf"("b"."Id") AS "p" ON TRUE
FbException: Dynamic SQL Error
SQL error code = -104
Token unknown - line 3, column 14
"PostsOf"
The uncorrelated form is fine, because it does not go through VisitCrossApply:
SELECT "p"."Heading", "p"."PostId" FROM "PostsOf"(@blogId) AS "p" -- works
Expected
JOIN LATERAL (SELECT * FROM "PostsOf"("b"."Id")) AS "p" ON TRUE
I ran both forms directly against Firebird 5.0.3 before reporting. The wrapped form returns the correct rows. The implicit form FROM "Blogs" b, "PostsOf"(b."Id") p also works, if that is preferable to a derived table.
Suggested fix
Add a TableValuedFunctionExpression branch beside the existing TableExpression one in both VisitCrossApply and VisitOuterApply, writing the function call inside (SELECT * FROM ...) and putting the alias on the derived table rather than on the call. I verified this by subclassing FbQuerySqlGenerator and replacing IQuerySqlGeneratorFactory: the query above then returns the expected rows.
Happy to open a PR if that would help.
Summary
FbQuerySqlGeneratoralready knows that Firebird will not accept a bare source afterLATERAL:VisitCrossApplyandVisitOuterApplyboth special-case aTableExpressionand emit(SELECT * FROM "T") AS "t". The same branch is missing for aTableValuedFunctionExpression, so a correlated queryable function is emitted as a bare call and the statement does not parse.I think this is the cause of the 14
[NotSupportedOnFirebirdFact]skips inUdfDbFunctionFbTests(all theQF_*Correlated*/QF_*Apply*ones). The store supports the query; only the generated SQL is malformed.Repro
EF Core 10.0.0,
FirebirdSql.EntityFrameworkCore.Firebird13.0.0, Firebird 5.0.3 embedded.Actual
The uncorrelated form is fine, because it does not go through
VisitCrossApply:Expected
I ran both forms directly against Firebird 5.0.3 before reporting. The wrapped form returns the correct rows. The implicit form
FROM "Blogs" b, "PostsOf"(b."Id") palso works, if that is preferable to a derived table.Suggested fix
Add a
TableValuedFunctionExpressionbranch beside the existingTableExpressionone in bothVisitCrossApplyandVisitOuterApply, writing the function call inside(SELECT * FROM ...)and putting the alias on the derived table rather than on the call. I verified this by subclassingFbQuerySqlGeneratorand replacingIQuerySqlGeneratorFactory: the query above then returns the expected rows.Happy to open a PR if that would help.