Skip to content

Inspecting a query that holds a three-tuple such as {Foo, :cache, [...]} raises, so Ecto.Query.CastError cannot be raised #4793

Description

@grempe

Elixir version

Elixir 1.20.1 (compiled with Erlang/OTP 29), Erlang/OTP 29 (erts 17.0.2)

Database and Version

PostgreSQL 18.6

Ecto Versions

ecto 3.14.2, ecto_sql 3.14.0

Database Adapter and Versions (postgrex, myxql, etc)

postgrex 0.22.4

Current behavior

Found by an AI assistant working with a human who was fuzz-testing their own application; reduced to the following. Present on master at 24f914a (checked 2026-09-15).

No repo or database is needed for inspect/1:

defmodule P do
  use Ecto.ParameterizedType
  def type(_), do: :string
  def init(opts), do: opts
  def cast(v, _), do: {:ok, v}
  def load(v, _, _), do: {:ok, v}
  def dump(v, _, _), do: {:ok, v}
end

import Ecto.Query
type = {:parameterized, {P, [match: {Foo, :cache, ["^[a-z]+$", []]}]}}
inspect(from p in "posts", where: p.title == type(^"abc", ^type), select: p.id)

The same tuple in an ordinary interpolated value reproduces it with a built-in type and no parameterized type:

Repo.all(from p in "posts", where: p.title == type(^{Foo, :cache, ["a", []]}, :string))

The three-tuple is how Spark stores a match: regex constraint ({Spark.Regex, :cache, [source, opts]}) and Ash's SQL layer copies a type's constraints into the Ecto type parameters, which is how it was hit; neither library is needed to reproduce.

inspect(query)
# "#Inspect.Error<got FunctionClauseError with message: no function clause matching in Access.get/3 ...>"
Repo.all(query)
# ** (FunctionClauseError) no function clause matching in Access.get/3

Repo.all/1 should have raised Ecto.Query.CastError (as it does for ^{1, 2, 3} or [match: "x"]); instead the message construction of Ecto.QueryError crashes. Params held in a map rather than a keyword list do not reproduce. On Elixir 1.18 through 1.20, Macro.to_string({Foo, :cache, ["a", []]}) raises the same FunctionClauseError (reported to Elixir separately; this report does not depend on it).

Failure point: Inspect.Ecto.Query.expr/3, lib/ecto/query/inspect.ex#L260-L267, and the interpolated-value clause at L307-L318, both ending in Macro.to_string/1; Ecto.QueryError and Ecto.SubQueryError interpolate the inspected query at lib/ecto/exceptions.ex#L36 and L78, and Ecto.Query.Planner.cast_param/6 builds a QueryError for the CastError message at lib/ecto/query/planner.ex#L1153-L1161.

Reproduction: * Description, with the failure point at this release: https://github.com/grempe/ash-fuzz-repros#ectoinspect-query-crashes-on-spark-regex-type-param

Expected behavior

inspect/1 succeeds and Ecto.Query.CastError is raised normally.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions