mfrasca

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

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

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

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

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

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

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)
mfrasca

mfrasca

I think I finally get your point. an AST (the value returned if I quote an expression) is a compile-time artifact, which I can compile, but not evaluate.

so instead of targeting the quote/AST, format, I should target the Ecto.Query format. and since I cannot do this from yecc (or can I? yecc is in Erlang), I need an intermediate representation. yet, for each production in the grammar (have you seen my grammar?), (production which combines simpler elements), I need a combining function/clause.

I will take a pause and read again in a few hours, but I have the impression that all the examples/hints I’ve been reading, they contain hard coded names or hard coded patterns.

Where Next?

Popular in Questions Top

dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New

Other popular topics Top

dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement