-
Notifications
You must be signed in to change notification settings - Fork 225
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
"Array contains element" translation #460
Comments
Can you please point to the documentation about this? Translating Also, it's important for SQL translations to be as close as possible to their C# source, so a difference in behavior (with regards to NULL for example) is likely not acceptable. |
Yeah, I agree that this NULL behavior difference is problematic. What kind of approach do you think would be good to take advantage of GIN indexes on arrays? |
I'm not an expert on PostgreSQL indices, but isn't it possible to use an expression index to speed up |
I don't this that expression based indexes can make a difference here. |
Why not? Do you have anything to back this up? The whole point of expression-based indexes is, well, that you can use them for any expression...
Of course it's possible to translate custom static functions to SQL - this is exactly what happens with |
Expression-based indexes can only be used if that exact same expression is present in the SQL. For example 'lower(name)'. But here we have value = ANY(array_column). There is no expression here that can be used in an index expression. As for the static functions, these would be mapped directly to these special PostgreSQL operators, so the user would not confuse Array.Contains(x) with these. @> is one of these operators, it can be used with several PostgreSQL types, so there could be several overloads of this static function: EF.PgOperator.Contains<T>(T[] container, T[] contained) There are other operators, like overlaps (&&). This could also used in a similar way: EF.PgOperator.Overlaps<T>(T[] array1, T[] array2) //true if have common value And with the appropriate index these expressions would use that index. |
OK, thanks for the links and the explanation - it's clear to me why It still seems that But it does seem clear that some way is needed to generate a Note that the range operators have already been added in version 2.1.0 (see #311). You can look at that implementation as the basis for your own PR for array operations. |
I have some work in progress on general translation support for array operators in #431, which currently supports this type of translation: src.Where(x => x.SomeList.All(y => x.SomeArray.Contains(y))); WHERE x."SomeList" <@ x."SomeArray" This should also work with an inline array: src.Where(x => new int[] { 3 }.All(y => x.SomeArray.Contains(y))); WHERE '{3}'::int[] <@ x."SomeArray" @havotto Would this form of translation support your use case? |
@austindrenski thanks, I forgot about your ongoing work, which I'll try to review ASAP :) Do you plan to provide translations for all of the array operations? |
@austindrenski Yes, this looks great. But I think that with array input syntax SQL parameters cannot be supported, like this: src.Where(x => new int[] { myIntValue }.All(y => x.SomeArray.Contains(y))); There is an alternative syntax for arrays, but I am not sure if it supports SQL parameters for elements: WHERE ARRAY[3]::int[] <@ x."SomeArray" |
@roji The big goal is to provide translations for the major ones by I would recommend reviewing with #444 before #431, as it splits out some of the generalized components that could be used by related features. @havotto You are correct that we translate arrays with the constructor syntax, rather than the literal syntax: -- What I wrote:
WHERE '{3}'::int[] <@ x."SomeArray"
-- What I should have written:
WHERE ARRAY[3]::int[] <@ x."SomeArray" I believe that we handle But, I'll add a test to confirm. |
@austindrenski and @havotto, note the specific behavior around NULL which the PostgreSQL array operators have (e.g. I don't think there should be an issue with supporting SQL parameters for array elements - we should definitely be able to translate that correctly. |
I would prefer the dedicated function approach. It's meaning is more straightforward for the user. |
@havotto, @roji Thank you both for pointing out the But, in general, I do prefer to see translations of standard LINQ patterns, rather than provider specific extensions. This is partly motivated by portability, but also by considerations for the consumer experience. If someone has In this case, I think there's room for both the extension methods and pattern translation under specific conditions. Indeed, part of what makes this operator set complicated is that the -- Example 1:
SELECT ARRAY[1,NULL] @> ARRAY[1,NULL];
?column?
----------
f
(1 row)
-- Example 2:
SELECT ARRAY[1,NULL] @> ARRAY[1];
?column?
----------
t
(1 row)
-- Example 3
SELECT ARRAY[1,NULL] @> ARRAY[]::int[];
?column?
----------
t
(1 row) From the C# side, we should be able to translate parameters of value type arrays. If the parameter array can't contain int[] myArray = new int[] { 0, 1, 2 };
src.Select(x => myArray.All(y => x.SomeArray.Contains(y)));
// or
src.Select(x => EF.Functions.Contains(x.SomeArray, myArray)); SELECT x."SomeArray" @> "myArray"; When a parameter array contains nullable values and those values can be verified to be non-null, then pattern translation should also apply: // We can verify that the elements are not null
string[] myArray = new string[] { "aaa", "bbb", "ccc" };
src.Select(x => myArray.All(y => x.SomeArray.Contains(y)));
// or
src.Select(x => EF.Functions.Contains(x.SomeArray, myArray)); SELECT x."SomeArray" @> "myArray"; There are a lot of cases to consider, but I think my approach for #431 moving forward will be:
|
You should thank @havotto instead :)
Of course we always prefer to translate standard .NET expressions rather than introducing new and specific functions. The guiding principle is simply to translate when the server-side behavior corresponds exactly to the client behavior, and not to do it otherwise. I think that portability here is a somewhat complex idea. In my opinion, whether an expression is executed client-side or server-side is also part of portability: imagine a theoretical application working well on PostgreSQL where an expression is server-evaluated, and when ported to MySQL breaks down completely because the expression is client-evaluated and breaks performance. In that sense, translating ordinary .NET expressions is actually a bit more dangerous, since it hides things: when you execute a special function from the Npgsql provider assembly, it's at least pretty clear that this function will only be server-evaluated in PostgreSQL. This doesn't mean we shouldn't translate ordinary .NET expressions (client evaluation warnings do exist for this), but it's primarily an experience/convenience thing. Regarding the idea of translating only value types, I'm a bit skeptical. It seems pretty confusing (and a little dangerous) for the exact same code to start being client-evaluated only because the type was switched from int to string (again don't forget the potentially huge impact on performance) - this is exactly the kind of murky behavior you referred to above. I'd rather have code that if it works, works in all cases - that means less subtlety and logic to understand on the user side. And don't forget that the ordinary .NET construct ( I'd also add that translating these complex LINQ expressions is more complicated than the usual simple function translation we do - it requires translating fragments and other things (#444) we haven't done yet, making the provider generally more complex and somewhat prone to break moving forward as EFCore evolves (as I wrote in #444, the services being replaced are currently internal in EF Core). To be clear, I'm OK in general with translating complex expressions/fragments, but there just doesn't seem to be enough value in what is currently on the table... Let me know your thoughts, and thanks as always for the great conversations and exchanges of ideas. |
I think I just hit this
Im migrating from SQL server and their provider translates this. |
Is there another way I can write the where clause here |
OK I think im hitting #1152 |
According to the documentation array element containment is translated like this:
.Where(c => c.SomeArray.Contains(3))
to
WHERE 3 = ANY("c"."SomeArray")
As I understand this filter cannot use GIN index created on the array column.
So what about changing the translation to:
WHERE "c"."SomeArray" @> ARRAY[3]
or
WHERE "c"."SomeArray" @> '{3}'
There is a difference with NULL element handling though.
The text was updated successfully, but these errors were encountered: