How do i produce an elixir struct from erlang

I’m still busy with a yecc parser, that means I’m in erlang, and I wonder how to produce elixir structures.

I know that maps like %{k: v, ...} are just elixir syntactic sugar on top of [{k, v} ...], but what about structs?

for example, I have this production, where I recognize a string as a domain, and the value I want to associate to a domain is a Ecto.Query.FromExpr. I only know how to return a string, while I wish to have %Ecto.Query.FromExpr{source: {$1, nil}}.

domain -> word : list_to_binary(extract_value('$1')).

extract_value({_Token, _Line, Value}) -> Value.

Maps are not syntactic sugar over lists, they are just plain maps, in Erlang #{}.

Structs are just maps with the :__struct__ key set to a module. From elixir side they are constructed to always have all specified keys set to their defaults.

4 Likes

you mean that the erlang equivalent would be this:

[{'__struct__', 'ecto-module-name'}, {k, v} ...]

?

No.

#{
  '__struct__' => 'Elixir.Plug.Conn',
  %% etc
}
1 Like

do you think you can help me with this error message?

message: "got FunctionClauseError with message \"no function clause matching in Inspect.Ecto.Query.from_sources/1\" while inspecting %{__struct__: Ecto.Query, from: %{__struct__: Ecto.Query.FromExpr, source: {\"plant\", nil}}, joins: {:==, [{{:., [], [{:domain, [], Elixir}, :id]}, [], []}, 3]}, select: [expr: [{{:., [], [{:&, [], [0]}, :id]}, [], []}]], wheres: {:==, [{{:., [], [{:domain, [], Elixir}, :id]}, [], []}, 3]}}"

please do not focus on the wheres and joins parts, I have not yet rewritten them.

but why do I see %{__struct__: Ecto.Query.FromExpr, instead of %Ecto.Query.FromExpr{

All elixir modules, which includes structs, have an Elixir. prefix. You don’t see this when you are writing literal modules in Elixir, but you’ll need to specify it from Erlang.

You need Elixir.Ecto.Query instead.

That indicates that there are either to much or to few fields compared to the defstruct.

1 Like
query -> domain where expression : #{'__struct__' => 'Elixir.Ecto.Query', from => '$1', select => [{expr, [{{'.', [], [{'&', [], [0]}, id]}, [], []}]}], wheres => '$3', joins => '$3'}.
domain -> word : #{'__struct__' => 'Elixir.Ecto.Query.FromExpr', source => {list_to_binary(extract_value('$1')), nil}}.

The posted snippet was alixir syntax, so the :Elixir prefix was there but implicit.

2 Likes

Take a look at https://hexdocs.pm/ecto/Ecto.Query.html#t:t/0, you are missing a lot of fields.

1 Like

I know I am not defining all fields. if I do this from Elixir, it works fine, as I wrote here.

I’m now trying to reproduce that, from the parser, in erlang.

the error message explicitly mentions from_sources, which I would like to understand.

Struct syntax in elixir does a lot of magic, you need to do that magic on your own when writing Erlang.

Especially you need to manually and explicitly specify each key with its default value.

2 Likes

well, I’m afraid I just managed to break that magic, but in a direction opposite as what I was aiming at.

within this project this Ecto.Query.FromExpr{source: {"plant", nil}} does not work any more:


iex(1)> w = %Ecto.Query{}
#Ecto.Query<from q in query>
iex(2)> w = %{ w | from: %Ecto.Query.FromExpr{source: {"plant", nil}}}
** (CompileError) iex:2: Ecto.Query.FromExpr.__struct__/1 is undefined, cannot expand struct Ecto.Query.FromExpr
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1355: :lists.mapfoldl/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1355: :lists.mapfoldl/3
iex(2)> %Ecto.Query.FromExpr{source: {"plant", nil}}
** (CompileError) iex:2: Ecto.Query.FromExpr.__struct__/1 is undefined, cannot expand struct Ecto.Query.FromExpr

iex(2)> 

It should exist and is defined here:

Anyway, @moduledoc false clearly states that this module and anything defined by it, is not meant to be used by anything outside of ecto.

It is considered internal use only. You need to find another way to create your %Ecto.Query{}

2 Likes

what can I say. I’m short of options. this is why I’m asking here, and on stackoverflow.

Have you considered asking the ecto team for a function based API to build queries at runtime rather than build time?

1 Like

you lost me. you mean that asking here is the way to go?

I mean that you should file a bug at the ecto repository, asking for a API that can be used to create queries at runtime (function based) rather the current macro-based API which does construction only during compile time.

1 Like

The API for query creation is exactly what we have today. You are thinking about using the internals but it is not necessary to fiddle with them all. Our query api supports both interpolation and dynamic values. For example, imagine you have to parse “foo:43” and convert it to a query. You can do this:

query = "table" # or Schema
[field, value] = String.split("foo:43", ":")
field = String.to_existing_atom(field)
query = from q in querry, field(q, ^field) == ^value

And so on. Ben mentioned the correct way to solve this in the forum here: How to produce executable code using yecc? - In a nutshell, you shouldn’t build the query inside the parser because the parser goal is to return an AST. Then, in Elixir code, you traverse the AST, building the Ecto query.

For example, for “foo:43”, your parser should return something like:

{:where, :foo, "43"}

Then you can traverse this AST and convert it into an Ecto query.

As a good exercise, I would suggest for you to implement a calculator using yecc. Do not have yecc perform the computations. Instead you should have yecc return an AST (so 1 + 2 returns {:+, 1, 2}) and then you traverse this AST in Elixir computing the final result.

4 Likes

But the OP wants to create an ecto query at runtime from user input. How shall he continue after his parser returned the AST?