Postgrex Types Error (working fine previously)

So I had previously run into a problem with Postgresql “money” type not being supported by default on postgrex. I solved that by creating a custom type and it has worked without problems for months. The database using this as a field type is not under my control, I just need to connect to it. There are 2 additional ecto repos on my side. I don’t use that type on my databases.

Now I’ve updated to elixir 1.9 (and started using the new releases, previously was using distillery) and I’m getting this error and I can’t figure out why (it also doesn’t seem to be related to that…).

Log:

[3202]: ** (MatchError) no match of right hand side value: false
[3202]:     (postgrex) lib/postgrex/types.ex:128: anonymous fn/3 in Postgrex.Types.associate_type_infos/2
[3202]:     (elixir) lib/enum.ex:1948: Enum."-reduce/3-lists^foldl/2-0-"/3
[3202]:     (postgrex) lib/postgrex/types.ex:127: Postgrex.Types.associate_type_infos/2
[3202]:     (postgrex) lib/postgrex/type_server.ex:122: Postgrex.TypeServer.associate/2
[3202]:     (stdlib) gen_server.erl:661: :gen_server.try_handle_call/4
[3202]:     (stdlib) gen_server.erl:690: :gen_server.handle_msg/6
[3202]:     (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
[3202]: Last message (from #PID<0.4393.0>): {:update, #Reference<0.3878870645.4258004993.73469>, [%Postgrex.TypeInfo{array_elem: 0, base_type: 0, comp_elems: [1043], input: "record_in", ....

(truncated it has a very long line of what I guess is type definitions from the db I’m connecting to…)

The type is being defined as such in another module:

Postgrex.Types.define(ExternalDB.PostgresTypes, [{Types.Money, [:decode_copy]}])

And the type itself is:

defmodule Types.Money do
  @behaviour Postgrex.Extension

  import Postgrex.BinaryUtils, warn: false

  @int8_range -9223372036854775808..9223372036854775807
  
  def init(opts) do
    Keyword.get(opts, :decode_copy, :copy)
  end

  def matching(_state), do: [type: "money"]

  def format(_state), do: :binary

  def encode(_) do
    range = Macro.escape(@int8_range)
    quote location: :keep do
      int when is_integer(int) and int in unquote(range) ->
        case int * 100 do
          n_int when n_int in unquote(range) ->
            <<8 :: int32, int :: int64>>
          other ->
            raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, unquote(range))
        end
      %Decimal{coef: coef} when is_integer(coef) and coef in unquote(range) ->
        <<8 :: int32, coef :: int64>>
      float when is_float(float) ->
        case Decimal.new(float) do
          %Decimal{coef: coef} when is_integer(coef) and coef in unquote(range) ->
            <<8 :: int32, coef :: int64>>
          %Decimal{coef: coef} ->
            raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(coef, unquote(range))
        end
      other ->
        raise DBConnection.EncodeError, Postgrex.Utils.encode_msg(other, unquote(range))
    end
  end

  def decode(_) do
    quote location: :keep do
      <<8 :: int32, int :: int64>> -> Decimal.div(Decimal.new(int), 100)
    end
  end

end

Then I set it up in a config that is used when starting a postgrex link.

new_config = %__MODULE__{base_config | table_type: :properties, config: List.keystore(base_config.config, :types, 0, {:types, ExternalDB.PostgresTypes})}

# ... somewhere else
{:ok, pid} = Postgrex.start_link(that_new_config)

Looking at https://github.com/elixir-ecto/postgrex/blob/9f068636277729e7f3314e4df5d7bd61fec6be13/lib/postgrex/types.ex#L133
Seems to indicate it is failing to insert a new ets entry for an oid (??)

for %TypeInfo{oid: oid} = type_info <- type_infos do
    true = :ets.insert_new(table, {oid, type_info, nil})
end

This has been working fine for a long time, from ecto 2 to 3 and I can’t understand what is the error.

1 Like

If you have no answer to this and you don’t control the field.

I think the worst case is you have to put raw sql and ecto let’s you put raw sql until you find a better solution.

What version of Ecto 2 were you on previously? Looks like 2.1+ requires Postgrex ~> 0.13.0, which would have the insert_new that’s causing your issue.

Relevant PR:

Thanks, I think I worded it wrong, it has worked when using ecto 2.X and has worked for a couple months with ecto 3.X as well. This error is happening on Postgrex (plain postgrex) used to connect to that external db (not using ecto)
That error seems to be happening on the protocol bootstrap/handshake (as in when I’m opening the connection)?

[2930]: 21:30:15.448 [error] GenServer {Postgrex.TypeManager, {Postgrex.DefaultTypes, {'database.url.com', 6432, "database_name"}}} terminating
[2930]: ** (MatchError) no match of right hand side value: false
[2930]: (postgrex) lib/postgrex/types.ex:128: anonymous fn/3 in Postgrex.Types.associate_type_infos/2
#...

If I take off that type definition, then I get this
[2930]: 21:30:10.268 [error] Postgrex.Protocol (#PID<0.3573.0>) disconnected: ** (Postgrex.QueryError) typemoneycan not be handled by the types module Postgrex.DefaultTypes
but if not mistaken only when actually querying so I might get it to work by removing the type and casting the field in the query passed to postgrex (not sure it makes the oid problem go away though).

The only thing is not being able to understand what is causing the issue in the first place.

[EDIT] it might also be something on the external DB but since the external DB is working I assume it’s something else on my side

1 Like

It’s something related to the improvements on the type server, I downgraded to 0.14.0 and it works fine again. I’m going to open a bug.

2 Likes