mfrasca
How to produce executable code using yecc?
maybe it’s not the way to go, but this was my initial guess.
I have been writing a parser for a reduced query dialect that I have partially inherited and much expanded, allowing users write things like:
plant where accession.code='2018.0047'
it’s not ready, but the missing intermediate steps are clear, except the final one: how do I have the result executed?
I am targeting as result the quote representation of the equivalent Ecto.Query.from query. for the above example the equivalent as far as I am concerned would be:
from(p in "plant", select: [:id], join: a in "accession", on: a.id==p.accession_id, where: a.code=="2018.0047")
I have been looking into the structures returned by the __schema__ functions, and all looks quite doable, I mean I know how to extract the table name from the modules, and owner and related modules and keys from the association given its name, so let’s assume that my parser does return this value:
{:from, [context: Elixir, import: Ecto.Query],
[
{:in, [context: Elixir, import: Kernel], [{:p, [], Elixir}, "plant"]},
[
select: [:id],
join: {:in, [context: Elixir, import: Kernel],
[{:a, [], Elixir}, "accession"]},
on: {:==, [context: Elixir, import: Kernel],
[
{{:., [], [{:a, [], Elixir}, :id]}, [], []},
{{:., [], [{:p, [], Elixir}, :accession_id]}, [], []}
]},
where: {:==, [context: Elixir, import: Kernel],
[{{:., [], [{:a, [], Elixir}, :code]}, [], []}, "2018.0047"]}
]
]}
how do I get Ecto to execute it?
Most Liked Responses
benwilson512
Sure, so let’s do some practice. What I don’t recommend is trying to look at the ecto functions and see how they work, because they’re pretty complicated internally. Technically they’re all macros, so it’s just a bit of a pain to figure out what is going on. There’s a reason for this (compile time protection from sql injection, yay!) but it’s definitely complicated.
Fortunatelly, using the ecto query macros isn’t that bad, but there’s no substitute for practice. I highly recommend working through the programming ecto book, or any of the other Elixir books that uses ecto (programming phoenix, the graphql book, etc). If you want to go for it just based on docs that’s fine too, just build some simple exercises for yourself. Here’s an example of a function from the graphql book that takes this kind of input:
%{filter: %{category: "drinks", priced_below: 10.00}, order: :desc}
And then builds an ecto query that filters / orders an Item schema accordingly:
def list_items(args) do
args
|> items_query
|> Repo.all
end
def items_query(args) do
Enum.reduce(args, Item, fn
{:order, order}, query ->
query |> order_by({^order, :name})
{:filter, filter}, query ->
query |> filter_with(filter)
end)
end
defp filter_with(query, filter) do
Enum.reduce(filter, query, fn
{:name, name}, query ->
from q in query, where: ilike(q.name, ^"%#{name}%")
{:priced_above, price}, query ->
from q in query, where: q.price >= ^price
{:priced_below, price}, query ->
from q in query, where: q.price <= ^price
{:added_after, date}, query ->
from q in query, where: q.added_on >= ^date
{:added_before, date}, query ->
from q in query, where: q.added_on <= ^date
{:category, category_name}, query ->
from q in query,
join: c in assoc(q, :category),
where: ilike(c.name, ^"%#{category_name}%")
{:tag, tag_name}, query ->
from q in query,
join: t in assoc(q, :tags),
where: ilike(t.name, ^"%#{tag_name}%")
end)
end
If we pass the example input into the items_query function we see that it returns an ecto query:
iex(2)> query = PlateSlate.Menu.items_query(%{filter: %{category: "drinks", priced_below: 10.00}, order: :desc})
#Ecto.Query<from i in PlateSlate.Menu.Item, join: c in assoc(i, :category),
where: ilike(c.name, ^"%drinks%"), where: i.price <= ^10.0,
order_by: [desc: i.name]>
This query can then be executed by our repo:
Repo.all(query)
I’m suggesting that you should build a function similar to my items_query function that recursively walks through your yecc output and matches on various sub parts, reducing on to the ecto query data structure.
benwilson512
Right, this is entirely possible in Elixir. Build a datastructure in Yecc that represents the input. Build a recursive function that builds an ecto query by walking the datastructure yecc returns. If this seems like something you aren’t sure how to do, break the problem down. Play around with the ecto functions so that you get familiar with how to use them. Play around with walking data structures with an accumulator so that you can do simple things like count how many parts to the input there are. Then combine.
We’re happy to help with these steps, but you need to break the problem down into pieces you can work on incrementally.
benwilson512
Elixir code is compiled not interpreted. Dynamically compiling code all the time is not going to perform well, and may lead to unbound memory growth since you’d be generating compilation artifacts all the time.
By an ecto query data structure I mean a %Ecto.Query{} struct, which is what you get when you call the functions:
SomeSchema |> where(foo: ^whatever)
Popular in Questions
Other popular 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
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #podcasts
- #code-sync
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








