From a254e09d59651c5d003b1d71fc32350140da655d Mon Sep 17 00:00:00 2001 From: nadilas Date: Fri, 9 Jan 2026 21:38:14 +0100 Subject: [PATCH] fix: oban lifeline plugin errors --- lib/ecto_libsql/native.ex | 39 +++++++++++++++++++++------------- lib/ecto_libsql/query.ex | 44 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/lib/ecto_libsql/native.ex b/lib/ecto_libsql/native.ex index 0c05de2..b145450 100644 --- a/lib/ecto_libsql/native.ex +++ b/lib/ecto_libsql/native.ex @@ -704,13 +704,17 @@ defmodule EctoLibSql.Native do num_rows end - # For INSERT/UPDATE/DELETE without RETURNING, columns and rows will be empty - # Set them to nil to match Ecto's expectations - {columns, rows} = - if command in [:insert, :update, :delete] and columns == [] and rows == [] do - {nil, nil} - else - {columns, rows} + # Normalize: columns MUST be a list, rows nil only for successful writes without RETURNING + columns = columns || [] + + rows = + cond do + command in [:insert, :update, :delete] and columns == [] and rows == [] and actual_num_rows > 0 -> + nil + rows == nil -> + [] + true -> + rows end result = %EctoLibSql.Result{ @@ -767,13 +771,17 @@ defmodule EctoLibSql.Native do "rows" => rows, "num_rows" => num_rows } -> - # For INSERT/UPDATE/DELETE without actual returned rows, normalise empty lists to nil - # This ensures consistency with non-transactional path - {columns, rows} = - if command in [:insert, :update, :delete] and columns == [] and rows == [] do - {nil, nil} - else - {columns, rows} + # Normalize: columns MUST be a list, rows nil only for successful writes without RETURNING + columns = columns || [] + + rows = + cond do + command in [:insert, :update, :delete] and columns == [] and rows == [] and num_rows > 0 -> + nil + rows == nil -> + [] + true -> + rows end result = %EctoLibSql.Result{ @@ -790,10 +798,13 @@ defmodule EctoLibSql.Native do end else # Use execute_with_transaction for INSERT/UPDATE/DELETE without RETURNING + # columns MUST be [] (not nil), rows should be nil case execute_with_transaction(trx_id, conn_id, statement, args_for_execution) do num_rows when is_integer(num_rows) -> result = %EctoLibSql.Result{ command: command, + columns: [], + rows: nil, num_rows: num_rows } diff --git a/lib/ecto_libsql/query.ex b/lib/ecto_libsql/query.ex index 02e6620..f782c16 100644 --- a/lib/ecto_libsql/query.ex +++ b/lib/ecto_libsql/query.ex @@ -38,7 +38,49 @@ defmodule EctoLibSql.Query do def describe(query, _opts), do: query - def encode(_query, params, _opts), do: params + # Convert Elixir types to SQLite-compatible values before sending to NIF + def encode(_query, params, _opts) do + Enum.map(params, &encode_param/1) + end + + defp encode_param(%DateTime{} = dt), do: DateTime.to_iso8601(dt) + defp encode_param(%NaiveDateTime{} = dt), do: NaiveDateTime.to_iso8601(dt) + defp encode_param(%Date{} = d), do: Date.to_iso8601(d) + defp encode_param(%Time{} = t), do: Time.to_iso8601(t) + defp encode_param(%Decimal{} = d), do: Decimal.to_string(d) + defp encode_param(value), do: value + + # Normalize results for ecto_sql compatibility. + # Rules: + # 1. columns MUST ALWAYS be a list (even empty []), NEVER nil + # 2. rows should be nil only for write commands without RETURNING that affected rows + # 3. For all other cases (SELECT, RETURNING queries), rows must be a list + def decode(_query, result, _opts) when is_map(result) do + columns = case Map.get(result, :columns) do + nil -> [] + cols when is_list(cols) -> cols + _ -> [] + end + + cmd = Map.get(result, :command) + rows = Map.get(result, :rows) + num_rows = Map.get(result, :num_rows, 0) + + rows = cond do + # Write commands that affected rows but have no RETURNING -> rows should be nil + cmd in [:insert, :update, :delete] and rows == [] and num_rows > 0 and columns == [] -> + nil + # All other cases: rows must be a list + rows == nil -> + [] + true -> + rows + end + + result + |> Map.put(:columns, columns) + |> Map.put(:rows, rows) + end def decode(_query, result, _opts), do: result end