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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions