Frogglet
I am trying to use where exists (), which requires a fragment in ecto. I need to use a different table name for the subquery in different situations, so I thought a variable that gets interpolated would be ideal. No, the value does not come from an unsafe source. I am aware of the dangers of SQL injection attacks and how to avoid them. I am selecting the value of the variable from another ecto schema inside a case expression. Surely there must be some way of doing this, without having to entirely disconnect from the Ecto abstractions we already depend on?
Here is an example:
def thing(schema) do
other_table = Other.Schema.__schema__(:source)
from(a in My.Schema,
where: fragment("exists (
SELECT 1
FROM #{other_table} o
WHERE o.column_name = ?)", ^a.my_field)
)
|> Repo.all()
end
And here is the error:
(Ecto.Query.CompileError) to prevent SQL injection attacks, fragment(...) does not allow strings to be interpolated as the first argument via the `^` operator, got: `"exists (\n SELECT 1\n FROM #{other_table} o\n WHERE o.column_name = ?)"
We are trying to use exists because it is significantly more performant than the alternatives that ecto provides for our use-case. We are reaching the scale where inefficient queries that can’t use indexes properly are becoming critical problems.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixirconf-us
- #elixir-ls
- #ai
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
mindok
Have you tried do the string interpolation ahead of building the query.
e.g.
I haven’t tested it myself, but it’s the first thing I’d try
hauleth
Are you by chance using PostgreSQL? If so then
SELECT 1 FROM table WHERE pattern LIMIT 1will be equally performant. Unfortunately there is no way to do string interpolation infragmentas IIRC it is compile-time checked for correctness. There wasfragment_unsafefor brief moment but IIRC it was removed.Frogglet
No, this does not seem to work either.
Frogglet
Thanks, but unfortunately this is actually meant to operate on an existing query that is being passed in, kind of like so (though this is greatly simplified):
It is basically an implementation of a grant-based authorization system. It runs on a lot of queries we make so performance is paramount. Honestly I guess I can find some way to get all the different possible strings constructed at compile time, but this is pretty frustrating. I feel like as an API it should simply accept the string I give it, or at least have some sort of escape hatch available.
The more we try to optimize our system for performance, the more we have become frustrated with Ecto. It is an excellent library, but it appears geared towards smaller-scale applications that treat the database more like simple data store rather than an equal and essential part of solving complex engineering problems. I of course knew that hand-writing SQL would be necessary eventually, but I just assumed ecto wouldn’t do things like this to prevent me from constructing queries manually, otherwise I would have gone for bare postgrex from the beginning.
LostKobrakai
Ecto supports subqueries natively in joins. Is there anything preventing you from refactoring the query to be joined instead of being a where condition?
Frogglet
Yes, unfortunately, in order to accomplish this with joins we have to use joins, conditions, and even sometimes unions that end up having significantly worse performance. Initially this logic was all correctly implemented using joins without any fragments. I am only now trying to rewrite things in order to save performance. I have tried a lot of different combinations to try to get the query planner to use various indexes but so far using
where existsandwhere not existsis the only strategy that has actually worked. The problem seems to come up a lot with left joins and conditions that exclude some of the resulting null values, along with a few other things. I want to avoid fragment usage wherever possible so I always try to use joins first, but sometimes I am forced to turn to fragments.Bottom line seems to be that sometimes using
existswith a subquery is significantly more performant than equivalent logic implemented with joins, though I can’t say I know exactly why. I’m sure some of it depends on what version of Postgres, but I’ve tried updating to the latest and still run into the same issues.Frogglet
For instance, this blog explains one case where using
existsgives a 20x speedup over the equivalent non-exists SQL: SQL Performance of Join and Where ExistsThere are a lot of other situations that arise like that one that are hard to generalize. You just sometimes run into them and need to switch over to using
exists.hauleth
Have you benchmarked it on your own with newer PSQL? You can also use
LATERALinJOINto simulateEXISTSwith current Ecto.josevalim
Hi @Frogglet! We don’t allow interpolation because it can be unsafe and lead to SQL injection attacks.
Have you tried passing the table name as fragment interpolation:
fragment(“... FROM ? ....”, ^table_name)?Frogglet
Hey, Jose, thanks for taking the time to help!
Yeah, unfortunately that doesn’t seem to work. I get this syntax error:
(Postgrex.Error) ERROR 42601 (syntax_error) syntax error at or near "$1"The sql in question is:
It doesn’t seem to like using an argument in the
FROMclause.