Closed kipcole9 closed 3 years ago
Thanks @kipcole9 for the report. This is a bit more complicated because you can't refer to a composite type like that. A composite type is always instantiated, so you need to refer to a proper instance.
Have you tried this approach:
query =
from(
organization in "organizations",
select: %{
total: type(sum(organization.payroll), organization.payroll)
}
)
You should be able to refer to an existing field to use its type.
@josevalim Unfortunately while that compile ok, it produces a query that attempts to cast to a type of any
which is invalid. The reason is that this is a schemaless query meaning that it has no type information that can be referred to from another field. In this case there is no organization.payroll
since there is no schema module.
query =
from(
organization in "organizations",
select: %{
total: type(sum(organization.payroll), organization.payroll)
}
)
1) test aggregate from a keyword query using a schemaLESS query (Money.DB.Test)
test/db_test.exs:76
** (Postgrex.Error) ERROR 42601 (syntax_error) syntax error at or near "any"
query: SELECT sum(o0."payroll")::any FROM "organizations" AS o0
code: assert Repo.all(query) == [%{total: Money.new(:USD, 300)}]
stacktrace:
(ecto_sql 3.5.4) lib/ecto/adapters/sql.ex:751: Ecto.Adapters.SQL.raise_sql_call_error/1
(ecto_sql 3.5.4) lib/ecto/adapters/sql.ex:684: Ecto.Adapters.SQL.execute/5
(ecto 3.5.7) lib/ecto/repo/queryable.ex:229: Ecto.Repo.Queryable.execute/4
(ecto 3.5.7) lib/ecto/repo/queryable.ex:17: Ecto.Repo.Queryable.all/3
test/db_test.exs:90: (test)
A test using a schema module passes, For example, a test with the following passes.
query =
from(
organization in Organization,
select: %{
total: type(sum(organization.payroll), organization.payroll)
}
)
I see. Yes, you will need to give it either a schema field or an instantiated parameterized schema type which you can do by calling this: https://github.com/elixir-ecto/ecto/commit/9dea16ac027c28bc275504f386d1efc7f91d2cdc
Unfortunately that recommendation does not work since that commit is not in a release published on hex, and using the return tuple explicitly is also raising an exception.
Ecto.ParameterizedType.init/2
does not exist.Ecto.ParameterizedType.init/2
raises. It appears that the type/2
macro does not support the 3-tuple. query =
from(
organization in "organizations",
select: %{
total: type(sum(organization.payroll),
{:parameterized, Money.Ecto.Composite.Type, []}
)
}
)
type/2
does not support the 3-tuple from Ecto.ParameterizedType.init/2
** (Ecto.Query.CompileError) type/2 expects an alias, atom or source.field as second argument, got: `{:parameterized, Money.Ecto.Composite.Type, []}`
(ecto 3.5.7) expanding macro: Ecto.Query.select/3
test/db_test.exs:83: Money.DB.Test."test aggregate from a keyword query using a schemaLESS query"/1
(ecto 3.5.7) expanding macro: Ecto.Query.from/2
test/db_test.exs:83: Money.DB.Test."test aggregate from a keyword query using a schemaLESS query"/1
(elixir 1.11.3) lib/kernel/parallel_compiler.ex:416: Kernel.ParallelCompiler.require_file/2
(elixir 1.11.3) lib/kernel/parallel_compiler.ex:316: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7
iex> Ecto.ParameterizedType.module_info
[
module: Ecto.ParameterizedType,
exports: [
__info__: 1,
"MACRO-__using__": 2,
behaviour_info: 1,
module_info: 0,
module_info: 1
],
Config: {:ecto, "~> 3.5"}
Locked version: 3.5.8
Releases: 3.5.8, 3.5.7, 3.5.6, 3.5.5, 3.5.4, 3.5.3, 3.5.2, 3.5.1, ...
Licenses: Apache-2.0
Links:
GitHub: https://github.com/elixir-ecto/ecto
My conclusions:
type/2
macro. If true, updated docs are requiredtype/2
macro needs updating to support the 3-tuple returned from Ecto.ParameterizedType.init/2
when it is releasedmatch?/2
macro could emit a better message if passed a parameterised type module. If true then a PR would be useful.I can tackle (1) and (3), not sure I understand the internals enough to tackle (2).
@kipcole9 does this work?
total: type(sum(organization.payroll), ^{:parameterized, Money.Ecto.Composite.Type, []})
Unfortunately not:
** (ArgumentError) unsupported type `{:{}, [line: 87], [:parameterized, {:__aliases__, [counter: {Money.DB.Test, 72}, line: 87], [:Money, :Ecto, :Composite, :Type]}, []]}`. The type can either be an atom, a string or a tuple of the form `{:map, t}` or `{:array, t}` where `t` itself follows the same conditions.
code: assert Repo.all(query) == [%{total: Money.new(:USD, 300)}]
stacktrace:
(ecto_sql 3.5.4) lib/ecto/adapters/postgres/connection.ex:1286: Ecto.Adapters.Postgres.Connection.ecto_to_db/1
(ecto_sql 3.5.4) lib/ecto/adapters/postgres/connection.ex:698: Ecto.Adapters.Postgres.Connection.expr/3
(ecto_sql 3.5.4) lib/ecto/adapters/postgres/connection.ex:1238: Ecto.Adapters.Postgres.Connection.intersperse_map/4
(ecto_sql 3.5.4) lib/ecto/adapters/postgres/connection.ex:319: Ecto.Adapters.Postgres.Connection.select/3
(ecto_sql 3.5.4) lib/ecto/adapters/postgres/connection.ex:113: Ecto.Adapters.Postgres.Connection.all/2
Fixed on master. Best option is to use master for now. :)
Thank you José, appreciate the support. Confirmed all tests on my side passing too. I'll send a PR to update the documentation for the type/2
soon.
Environment
Current behavior
As reported by @emaiax on the ex_money_sql repo, using the
type/2
macro in a schema-less query with a customEcto.ParameterizedType
results in a call to Ecto.Type.match?/2 which in turns calls schema_type.type/0 which for a parameterised type does not exist, thereby raising an exception.Example
Reported exception
Money.Ecto.Composite.Type
The Money.Ecto.Composite.Type implements
type/1
as required. Addingtype/0
to satisfymatch?/2
seems to cause Ecto to treat the type as a non-parameterised type so I don't think thats an option in this case.To reproduce
config/test.exs
to suitmix deps.get
mix test
Expected behavior
That
match?/2
should work with parameterised types and schema less queries. Perhapsschema_type.type
should also beschema_type.type()
?