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 摘要。