Skip to content

Can't Store Array of Domain Over Composite Type #766

@dcuddeback

Description

@dcuddeback

Elixir version

Erlang/OTP 28 [erts-16.1.1] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]

Database and Version

postgres (PostgreSQL) 18.0

Postgrex Version

postgrex 0.22.0 (Hex package) (mix)

Current behavior

  1. Create a composite type and a domain over that composite type to enforce some constraints. Create an example table using the domain type:
demodule CreateExampleTable do
  use Ecto.Migration

  def up do
    execute("""
      CREATE TYPE composite_example AS (
          a text,
          b integer
      )
    """)

    execute("""
      CREATE DOMAIN non_null_composite AS composite_example
      CHECK (
          (VALUE).a IS NOT NULL
      AND (VALUE).b IS NOT NULL)
    """)

    create table("examples") do
      add :single, :non_null_composite
      add :multiple, {:array, :non_null_composite}
    end
  end

  def down do
    drop table("examples")

    execute("DROP DOMAIN non_null_composite")
    execute("DROP TYPE composite_example")
  end
end
  1. Define custom Ecto.Type:
defmodule CompositeExample do
  use Ecto.Type

  defstruct [:a, :b]

  @impl true
  def type(), do: :non_null_composite

  @impl true
  def cast(%__MODULE__{} = value) do
    {:ok, value}
  end
  def cast({a, b}) do
    {:ok, %__MODULE__{a: a, b: b}}
  end
  def cast(_) do
    :error
  end

  @impl true
  def dump(%__MODULE__{a: a, b: b}) do
    {:ok, {a, b}}
  end

  @impl true
  def load({a, b}) do
    {:ok, %__MODULE__{a: a, b: b}}
  end
end
  1. Define schema:
defmodule Example do
  use Ecto.Schema

  @primary_key false

  schema "examples" do
    field :single, CompositeExample
    field :multiple, {:array, CompositeExample}
  end
end
  1. Insert records into the schema:
Repo.insert!(%Example{single: %CompositeExample{a: "a", b: 1}})
Repo.insert!(%Example{multiple: [%CompositeExample{a: "a", b: 1}, %CompositeExample{a: "b", b: 2}]})
  1. The first insert is successful (I can use the domain type as a column type).
[debug] QUERY ERROR source="examples" db=4.4ms queue=0.3ms idle=42.5ms
INSERT INTO "examples" ("single") VALUES ($1) [%CompositeExample{a: "a", b: 1}]

The second insert (using the domain type in an array) raises an error about encoding an anonymous tuple:

↳ :elixir_compiler_10.__FILE__/1, at: demo.exs:3
** (DBConnection.EncodeError) cannot encode anonymous tuple {"a", 1}. Please define a custom Postgrex extension that matches on its underlying type:

    use Postgrex.BinaryExtension, type: "typeinthedb"

    (postgrex 0.22.0) lib/postgrex/type_module.ex:194: Postgrex.DefaultTypes.encode_tuple/3
    (postgrex 0.22.0) lib/postgrex/type_module.ex:1084: Postgrex.DefaultTypes.encode_params/3
    (postgrex 0.22.0) lib/postgrex/query.ex:73: DBConnection.Query.Postgrex.Query.encode/3
    (db_connection 2.9.0) lib/db_connection.ex:1440: DBConnection.encode/5
    (db_connection 2.9.0) lib/db_connection.ex:1530: DBConnection.run_prepare_execute/5
    (db_connection 2.9.0) lib/db_connection.ex:1587: DBConnection.run_with_retries/5
    (db_connection 2.9.0) lib/db_connection.ex:791: DBConnection.parsed_prepare_execute/5
    (db_connection 2.9.0) lib/db_connection.ex:783: DBConnection.prepare_execute/4
    (postgrex 0.22.0) lib/postgrex.ex:298: Postgrex.query/4
    (ecto_sql 3.13.4) lib/ecto/adapters/sql.ex:1181: Ecto.Adapters.SQL.struct/10
    (ecto 3.13.5) lib/ecto/repo/schema.ex:1000: Ecto.Repo.Schema.apply/4
    (ecto 3.13.5) lib/ecto/repo/schema.ex:500: anonymous fn/15 in Ecto.Repo.Schema.do_insert/4
    (ecto 3.13.5) lib/ecto/repo/schema.ex:381: Ecto.Repo.Schema.insert!/4

Expected behavior

I wanted both inserts to succeed. This example actually works if I use the composite type in the array instead of the domain type. It's only when switching to the domain type inside the array that I get this error.

At first, I thought maybe domain types aren't supported by Postgrex, but I found this merged PR from a long time ago that added support specifically for domain types inside of arrays: #337. Maybe a regression of #337, or an edge-case of how domain types, composite types, and arrays interact with each other?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions