aesmail / aesmail/kaffy

[BUG] Listing for schemas with `primary_key: false` in migration causes kaffy traceback

未关闭
#330 0 条评论 0 个 reaction 已指派 1 人 已被 @aesmail 认领 在 GitHub 查看
bug
主要语言
Elixir
星标
1.4k
派生
171
PR 合并指标
30 天内没有已合并 PR

描述

**Versions Used**
Kaffy: 0.10.2
Phoenix: 1.17.14
Elixir: 1.17.3

**What's actually happening?**

I'm trying to make a many-to-many relation between users and groups, the migration looks something like this

```
create table(:user_group_rel, primary_key: false) do
add :user_id, references(:users, on_delete: :nothing), primary_key: true
add :group_id, references(:user_groups, on_delete: :nothing), primary_key: true
...
```

This currently does not work for listing such user_group relationships in the kaffy interace, and the workaround I'm rolling with for now is to not pass `primary_key: false` for the table creation, and keep around the `id` field.

I think this is related to #190, though that bug dealt with `embedded_schema`, and predates my time using Phoenix, so
I'm out of my depth if the issue I'm seeing is similar enough too what was previously reported there.

Here's the full stack trace of going to the kaffy page for this

# Postgrex.Error at GET /admin/accounts/user_group_rel

Exception:

** (Postgrex.Error) ERROR 42703 (undefined_column) column u0.id does not exist

query: SELECT u0."id", u0."user_id", u0."group_id", u0."inserted_at", u0."updated_at" FROM "user_group_rel" AS u0 ORDER BY u0."id" DESC LIMIT $1 OFFSET $2
(ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:1096: Ecto.Adapters.SQL.raise_sql_call_error/1
(ecto_sql 3.12.1) lib/ecto/adapters/sql.ex:994: Ecto.Adapters.SQL.execute/6
(ecto 3.12.4) lib/ecto/repo/queryable.ex:232: Ecto.Repo.Queryable.execute/4
(ecto 3.12.4) lib/ecto/repo/queryable.ex:19: Ecto.Repo.Queryable.all/3
(kaffy 0.10.2) lib/kaffy/resource_query.ex:34: Kaffy.ResourceQuery.list_resource/3
(kaffy 0.10.2) lib/kaffy_web/controllers/resource_controller.ex:70: KaffyWeb.ResourceController.index/2
(kaffy 0.10.2) lib/kaffy_web/controllers/resource_controller.ex:1: KaffyWeb.ResourceController.action/2
(kaffy 0.10.2) lib/kaffy_web/controllers/resource_controller.ex:1: KaffyWeb.ResourceController.phoenix_controller_pipeline/2
(phoenix 1.7.14) lib/phoenix/router.ex:484: Phoenix.Router.__call__/5
(my 0.1.5) lib/my_web/endpoint.ex:1: MyWeb.Endpoint.plug_builder_call/2
(my 0.1.5) deps/plug/lib/plug/debugger.ex:136: MyWeb.Endpoint."call (overridable 3)"/2
(my 0.1.5) lib/my_web/endpoint.ex:1: MyWeb.Endpoint.call/2
(phoenix 1.7.14) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
(plug_cowboy 2.7.2) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
(cowboy 2.12.0) /home/pi/code/my/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
(cowboy 2.12.0) /home/pi/code/my/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
(cowboy 2.12.0) /home/pi/code/my/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
(stdlib 6.0) proc_lib.erl:323: :proc_lib.init_p_do_apply/3

Code:

`lib/ecto/adapters/sql.ex`

1091 defp raise_sql_call_error(%DBConnection.OwnershipError{} = err) do
1092 message = err.message <> "\nSee Ecto.Adapters.SQL.Sandbox docs for more information."
1093 raise %{err | message: message}
1094 end
1095
1096> defp raise_sql_call_error(err), do: raise(err)
1097
1098 @doc false
1099 def reduce(adapter_meta, statement, params, opts, acc, fun) do
1100 %{pid: pool, telemetry: telemetry, sql: sql, opts: default_opts} = adapter_meta
1101 opts = with_log(telemetry, params, opts ++ default_opts)

`lib/ecto/adapters/sql.ex`

989 end
990
991 @doc false
992 def execute(prepare, adapter_meta, query_meta, prepared, params, opts) do
993 %{num_rows: num, rows: rows} =
994> execute!(prepare, adapter_meta, prepared, params, put_source(opts, query_meta))
995
996 {num, rows}
997 end
998
999 defp execute!(prepare, adapter_meta, {:cache, update, {id, prepared}}, params, opts) do

`lib/ecto/repo/queryable.ex`

227 assocs: assocs,
228 from: from
229 } = select
230
231 preprocessor = preprocessor(from, preprocess, adapter)
232> {count, rows} = adapter.execute(adapter_meta, query_meta, prepared, dump_params, opts)
233 postprocessor = postprocessor(from, postprocess, take, adapter)
234
235 {count,
236 rows
237 |> Ecto.Repo.Assoc.query(assocs, sources, preprocessor)

`lib/ecto/repo/queryable.ex`

14 query =
15 queryable
16 |> Ecto.Queryable.to_query()
17 |> Ecto.Query.Planner.ensure_select(true)
18
19> execute(:all, name, query, tuplet) |> elem(1)
20 end
21
22 def stream(_name, queryable, {adapter_meta, opts}) do
23 %{adapter: adapter, cache: cache, repo: repo} = adapter_meta
24

`lib/kaffy/resource_query.ex`

29 case Kaffy.ResourceAdmin.custom_index_query(conn, resource, paged) do
30 {custom_query, opts} ->
31 {Kaffy.Utils.repo().all(custom_query, opts), opts}
32
33 custom_query ->
34> {Kaffy.Utils.repo().all(custom_query), []}
35 end
36
37 do_cache = if search == "" and Enum.empty?(filtered_fields), do: true, else: false
38 all_count = cached_total_count(schema, do_cache, all, opts)
39 {all_count, current_page}

`lib/kaffy_web/controllers/resource_controller.ex`

65 false ->
66 unauthorized_access(conn)
67
68 true ->
69 fields = Kaffy.ResourceAdmin.index(my_resource)
70> {filtered_count, entries} = Kaffy.ResourceQuery.list_resource(conn, my_resource, params)
71 items_per_page = Map.get(params, "limit", "100") |> String.to_integer()
72 page = Map.get(params, "page", "1") |> String.to_integer()
73 has_next = round(filtered_count / items_per_page) > page
74 next_class = if has_next, do: "", else: " disabled"
75 has_prev = page >= 2

`lib/kaffy_web/controllers/resource_controller.ex`

1> defmodule KaffyWeb.ResourceController do
2 @moduledoc false
3
4 use Phoenix.Controller, namespace: KaffyWeb
5 use Phoenix.HTML
6 alias Kaffy.Pagination

`lib/kaffy_web/controllers/resource_controller.ex`

1> defmodule KaffyWeb.ResourceController do
2 @moduledoc false
3
4 use Phoenix.Controller, namespace: KaffyWeb
5 use Phoenix.HTML
6 alias Kaffy.Pagination

`lib/phoenix/router.ex`

479 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
480 halted_conn
481
482 %Plug.Conn{} = piped_conn ->
483 try do
484> plug.call(piped_conn, plug.init(opts))
485 else
486 conn ->
487 measurements = %{duration: System.monotonic_time() - start}
488 metadata = %{metadata | conn: conn}
489 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)

`lib/my_web/endpoint.ex`

1> defmodule MyWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :my
3 # temporarily disable
4 #use ErrorTracker.Integrations.Plug
5
6

`deps/plug/lib/plug/debugger.ex`

131 case conn do
132 %Plug.Conn{path_info: ["__plug__", "debugger", "action"], method: "POST"} ->
133 Plug.Debugger.run_action(conn)
134
135 %Plug.Conn{} ->
136> super(conn, opts)
137 end
138 rescue
139 e in Plug.Conn.WrapperError ->
140 %{conn: conn, kind: kind, reason: reason, stack: stack} = e
141 Plug.Debugger.__catch__(conn, kind, reason, stack, @plug_debugger)

`lib/my_web/endpoint.ex`

1> defmodule MyWeb.Endpoint do
2 use Phoenix.Endpoint, otp_app: :my
3 # temporarily disable
4 #use ErrorTracker.Integrations.Plug
5
6

`lib/phoenix/endpoint/sync_code_reload_plug.ex`

17
18 def call(conn, {endpoint, opts}), do: do_call(conn, endpoint, opts, true)
19
20 defp do_call(conn, endpoint, opts, retry?) do
21 try do
22> endpoint.call(conn, opts)
23 rescue
24 exception in [UndefinedFunctionError] ->
25 case exception do
26 %UndefinedFunctionError{module: ^endpoint} when retry? ->
27 # Sync with the code reloader and retry once

`lib/plug/cowboy/handler.ex`

6 def init(req, {plug, opts}) do
7 conn = @connection.conn(req)
8
9 try do
10 conn
11> |> plug.call(opts)
12 |> maybe_send(plug)
13 |> case do
14 %Plug.Conn{adapter: {@connection, %{upgrade: {:websocket, websocket_args}} = req}} = conn ->
15 {handler, state, cowboy_opts} = websocket_args
16 {__MODULE__, copy_resp_headers(conn, req), {handler, state}, cowboy_opts}

`/home/pi/code/my/deps/cowboy/src/cowboy_handler.erl`

32 -optional_callbacks([terminate/3]).
33
34 -spec execute(Req, Env) -> {ok, Req, Env}
35 when Req::cowboy_req:req(), Env::cowboy_middleware:env().
36 execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) ->
37> try Handler:init(Req, HandlerOpts) of
38 {ok, Req2, State} ->
39 Result = terminate(normal, Req2, State, Handler),
40 {ok, Req2, Env#{result => Result}};
41 {Mod, Req2, State} ->
42 Mod:upgrade(Req2, Env, Handler, State);

`/home/pi/code/my/deps/cowboy/src/cowboy_stream_h.erl`

301 end.
302
303 execute(_, _, []) ->
304 ok;
305 execute(Req, Env, [Middleware|Tail]) ->
306> case Middleware:execute(Req, Env) of
307 {ok, Req2, Env2} ->
308 execute(Req2, Env2, Tail);
309 {suspend, Module, Function, Args} ->
310 proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);
311 {stop, _Req2} ->

`/home/pi/code/my/deps/cowboy/src/cowboy_stream_h.erl`

290 %% to simplify the debugging of errors. The proc_lib library
291 %% already adds the stacktrace to other types of exceptions.
292 -spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok.
293 request_process(Req, Env, Middlewares) ->
294 try
295> execute(Req, Env, Middlewares)
296 catch
297 exit:Reason={shutdown, _}:Stacktrace ->
298 erlang:raise(exit, Reason, Stacktrace);
299 exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown ->
300 erlang:raise(exit, {Reason, Stacktrace}, Stacktrace)

`proc_lib.erl`

No code available.

## Connection details

### Params

%{"context" => "accounts", "resource" => "user_group_rel"}

### Request info

* URI: http://redacted:80/admin/accounts/user_group_rel
* Query string:

**What should happen instead?**

It should be possible to show list items that do not have an `id` key.

Thank you for taking a look and for sharing `kaffy` with the world.

贡献指南

这个仓库没有索引到贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。