Select struct fields with fragments

Is it possible to use a fragment to modify the value of a column when doing a select into a struct? For reasons beyond my control, I need to do something like this:

from(c in Company, select: struct(c, [:id, created_at: fragment("(? AT TIME ZONE 'UTC')::timestamp without time zone", c.created_at)]))

That incantation fails with undefined function fragment/2. I can ALMOST get what I need by doing

from(c in Company, select: %{struct(c, [:id]) | created_at: fragment("(? AT TIME ZONE 'UTC')::timestamp without time zone", c.created_at)}) |> Repo.all

But that gives me an Erlang datetime in the created_at field (I assume because I’m using the map override and at that point Ecto no longer knows what type created_at is supposed to have).


1 Like

I’m afraid I can’t help with your question, but I love the word “incantation” to describe a code fragment :joy: :+1:

1 Like

Yeah, you will have to manually convert that into an Ecto.DateTime or Elixir’s DateTime. Unfortunately we will be able to return proper Elixir calendar types only on Ecto 3.0.

1 Like

Thanks, @josevalim. Even if Ecto returned Ecto.DateTime (or etc) by default, in theory once I do %{.. | created_at: } don’t I lose the context of the original schema model? For example, if I had a custom type specified in my schema, the query wouldn’t know about the custom type inside the context of the map update because it’s a plain old map update. Or does the query retain that context somehow and under the hood it’s doing something more sophisticated than map update?

Also, is there a way to do the conversion from within the query? I tried %{... | created_at: Ecto.DateTime.from_erl(fragment(...))} but this didn’t work. I suppose the query would have to understand what bits of work to do after it has results vs what is part of the query itself, but it seems like there’s already some of this going on.

I’m mostly asking these questions to improve my own understanding, so I really appreciate the responses.

Thanks again!

1 Like

Yes, you would lose context. So if you using field :created_at, SomeSpecialDate, it wouldn’t return that particulate date but the new default one. My previous point was just there is no reason to use tuples since Elixir v1.3, but we cannot change Ecto as it would be backwards incompatible.

Nope, you need to do a later pass.