Skip to content

Conversation

@nadilas
Copy link

@nadilas nadilas commented Jan 9, 2026

I'm not well versed enough in elixir to make this a proper PR, so if you feel like it, I'm happy to learn how to make this proper.
In case you don't want it, feel free to throw it away.

I added Oban to a small project and ran into a few issues:

Ecto migrations:

** (CaseClauseError) no case clause matching:
{:ok, %EctoLibSql.Result{command: :insert, columns: nil, rows: [], num_rows: 1}}

Datetime handling:

(EctoLibSql.Error) Unsupported argument type: #{microsecond=>{...},second=>35,
calendar=>'Elixir.Calendar.ISO',month=>1,'__struct__'=>'Elixir.DateTime',...}

And Oban's Lifeline plugin:

protocol Enumerable not implemented for Atom. Got value: nil

As best I could understand:

  • columns was set to nil for write commands, but Ecto always expects a list
  • rows was nil for UPDATE...RETURNING with 0 matching rows, but Ecto expects a list

Wonderful AI quick fixes were, (sorry if they're trash 😅):

  • encode/3: Convert DateTime/NaiveDateTime/Date/Time/Decimal to ISO8601/string (NIF can't serialize these)
  • decode/3: columns is always [], rows is nil for successful writes without RETURNING, otherwise []
  • do_query/7 and do_execute_with_trx/6: columns is always [], rows is nil for successful writes without RETURNING, otherwise []

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved handling of query results for INSERT, UPDATE, and DELETE operations to ensure consistent result structures.
    • Enhanced parameter encoding for date and time types to ensure proper SQLite compatibility.
    • Fixed result normalisation to reliably process query outcomes across different operation types.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

Walkthrough

Query result normalisation is adjusted across transactional and non-transactional paths to ensure consistent column and row handling. Parameter encoding is enhanced to transform Elixir types into SQLite-compatible representations, whilst result decoding incorporates normalisation logic for improved ecto_sql compatibility.

Changes

Cohort / File(s) Summary
Query Result Normalisation
lib/ecto_libsql/native.ex
Normalises columns to list, adjusts rows handling for INSERT/UPDATE/DELETE operations without RETURNING clause, and updates result construction to use actual_num_rows when available.
Parameter Encoding and Result Decoding
lib/ecto_libsql/query.ex
Transforms parameter encoding to apply per-parameter type conversion (DateTime, NaiveDateTime, Date, Time, Decimal to SQLite representations) and introduces result normalisation logic with special handling for command-specific row behaviour.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Through SQLite's garden where queries now hop,
With parameters encoded from top to proper stop,
Columns normalised, rows kept just right,
Each result shaped perfectly—oh, what a sight!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'fix: oban lifeline plugin errors' does not match the actual changes, which address fundamental data normalization issues (columns/rows handling) and parameter encoding across multiple modules to fix Ecto compatibility. Revise the title to reflect the core changes, e.g. 'fix: normalize query result columns and rows, encode temporal types for sqlite compatibility' or similar to accurately describe the scope of the changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/ecto_libsql/native.ex (1)

800-815: Fix execute_with_transaction to return rows: [] for 0-row writes, not rows: nil.

The execute path inconsistently returns rows: nil for all INSERT/UPDATE/DELETE operations without RETURNING, even when num_rows == 0. The documented design rule (in Query.decode, line 56) specifies that rows should be nil only for write commands "that affected rows" (i.e., when num_rows > 0). The query path correctly normalises rows: nil to rows: [] for 0-row writes; the execute path should do the same.

Proposed fix
     else
       # Use execute_with_transaction for INSERT/UPDATE/DELETE without RETURNING
-      # columns MUST be [] (not nil), rows should be nil
+      # columns MUST be [] (not nil); rows should be nil only when rows were affected
       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,
+            rows: if(num_rows > 0, do: nil, else: []),
             num_rows: num_rows
           }
🤖 Fix all issues with AI agents
In @lib/ecto_libsql/query.ex:
- Around line 53-83: The decode/3 function can let non-list :rows pass through
and later crash; update decode/3 (the clauses handling columns, cmd, rows,
num_rows) to coerce rows to a list when it's non-nil and not a list (e.g. treat
any non-list rows as [rows] or convert via List.wrap semantics) before applying
the existing cond that sets nil for write commands with no RETURNING, and also
change the comment header text from "Normalize results" to British/Australian
spelling "Normalise results" to match guidelines.
- Around line 41-44: The encode/3 clause currently assumes params is a list and
calls Enum.map, which crashes for nil or non-list params; add a guarded clause
so def encode(_query, params, _opts) when is_list(params) do Enum.map(params,
&encode_param/1) end and provide a fallback def encode(_query, params, _opts)
when not is_list(params) do params end (or otherwise return the unmodified
params) to avoid raising before the NIF; reference the encode/3 function and
encode_param/1 helper.
🧹 Nitpick comments (2)
lib/ecto_libsql/native.ex (2)

707-718: Normalisation logic looks right, but fix en-AU spelling (“Normalise”).
This block aligns with the goal (columns always list; rows nil only for successful writes without RETURNING). The only change I’d request here is comment spelling per guidelines.


774-785: Same: behaviour is good; consider extracting a shared normaliser.
The query-path normalisation matches the non-transactional path; to avoid future drift, consider a small private helper (even just within this module) used by both blocks.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8c2486 and a254e09.

📒 Files selected for processing (2)
  • lib/ecto_libsql/native.ex
  • lib/ecto_libsql/query.ex
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ex,exs}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ex,exs}: Use British/Australian English for all Elixir code comments and documentation (except SQL keywords and compatibility requirements)
Always format Elixir code before committing using mix format --check-formatted

Use camelCase for function names in Elixir (e.g., defmodule, def handle_execute)

Files:

  • lib/ecto_libsql/native.ex
  • lib/ecto_libsql/query.ex
lib/ecto_libsql/native.ex

📄 CodeRabbit inference engine (CLAUDE.md)

Provide NIF stubs and safe wrapper functions in the Native module for all Rust NIF functions

Files:

  • lib/ecto_libsql/native.ex
**/*.{ex,exs,rs,md}

📄 CodeRabbit inference engine (CLAUDE.md)

When ending a work session, complete the mandatory workflow: file issues for remaining work, run quality gates (tests, linters, builds), update issue status, commit changes with git commit, clean up stashes, and provide context for next session

Files:

  • lib/ecto_libsql/native.ex
  • lib/ecto_libsql/query.ex
**/*.ex

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ex: Use camelCase for variable names in Elixir modules
All database connection errors and query failures must be handled gracefully with pattern matching on {:ok, ...} and {:error, ...} tuples - never use bare unwrap operations
Use prepared statements with automatic statement caching for repeated queries to achieve ~10-15x performance improvement
Use batch operations (batch_transactional or execute_batch_sql) for bulk inserts/updates instead of individual queries
Named parameters (:name, @name, $name syntax) are preferred over positional parameters (?) for readability and maintainability in complex queries
Use savepoints for partial rollback within transactions to enable error recovery patterns in batch operations
Use cursor streaming for large result sets to avoid loading entire datasets into memory
JSON functions (extract, type, set, replace, insert, remove, patch) should be used for efficient JSON/JSONB manipulation in LibSQL 3.45.1+
Enable foreign key constraints with EctoLibSql.Pragma.enable_foreign_keys/1 in production environments
Use WAL (Write-Ahead Logging) mode for better concurrency in production: EctoLibSql.Pragma.set_journal_mode(state, :wal)

Files:

  • lib/ecto_libsql/native.ex
  • lib/ecto_libsql/query.ex
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T06:36:01.585Z
Learning: Applies to lib/ecto/adapters/libsql/connection.ex : Add SQLite function support by defining expression handlers for native SQLite functions
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T06:36:01.585Z
Learning: Applies to native/ecto_libsql/src/**/*.rs : Validate transaction ownership by checking that `entry.conn_id == conn_id` before performing transaction operations
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T06:36:01.585Z
Learning: Applies to lib/ecto/adapters/libsql.ex : Implement type dumpers for converting Ecto types to SQLite values using `dumpers/2` function
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T06:36:01.585Z
Learning: Applies to lib/ecto/adapters/libsql.ex : Implement type loaders for converting SQLite values to Ecto types using `loaders/2` function
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T04:56:27.393Z
Learning: Applies to priv/repo/migrations/*.exs : Use STRICT tables in migrations for type safety when database schema stability is critical
📚 Learning: 2026-01-06T06:36:01.585Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T06:36:01.585Z
Learning: Applies to native/ecto_libsql/src/**/*.rs : Validate transaction ownership by checking that `entry.conn_id == conn_id` before performing transaction operations

Applied to files:

  • lib/ecto_libsql/native.ex
📚 Learning: 2026-01-06T06:36:01.585Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T06:36:01.585Z
Learning: Applies to lib/ecto/adapters/libsql/connection.ex : Add SQLite function support by defining expression handlers for native SQLite functions

Applied to files:

  • lib/ecto_libsql/query.ex
📚 Learning: 2026-01-06T06:36:01.585Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T06:36:01.585Z
Learning: Applies to lib/ecto/adapters/libsql.ex : Implement type dumpers for converting Ecto types to SQLite values using `dumpers/2` function

Applied to files:

  • lib/ecto_libsql/query.ex
📚 Learning: 2026-01-06T06:36:01.585Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T06:36:01.585Z
Learning: Applies to lib/ecto_libsql/native.ex : Provide NIF stubs and safe wrapper functions in the Native module for all Rust NIF functions

Applied to files:

  • lib/ecto_libsql/query.ex
📚 Learning: 2026-01-06T06:36:01.585Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-06T06:36:01.585Z
Learning: Applies to lib/ecto/adapters/libsql.ex : Implement type loaders for converting SQLite values to Ecto types using `loaders/2` function

Applied to files:

  • lib/ecto_libsql/query.ex
📚 Learning: 2026-01-08T04:56:27.394Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-08T04:56:27.394Z
Learning: Applies to **/*.ex : All database connection errors and query failures must be handled gracefully with pattern matching on `{:ok, ...}` and `{:error, ...}` tuples - never use bare unwrap operations

Applied to files:

  • lib/ecto_libsql/query.ex
🧬 Code graph analysis (1)
lib/ecto_libsql/native.ex (1)
native/ecto_libsql/src/transaction.rs (1)
  • execute_with_transaction (283-307)

Comment on lines +41 to +44
# Convert Elixir types to SQLite-compatible values before sending to NIF
def encode(_query, params, _opts) do
Enum.map(params, &encode_param/1)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file structure and locate the query.ex file
find . -name "query.ex" -type f

Repository: ocean/ecto_libsql

Length of output: 87


🏁 Script executed:

# Let's read the file to see the encode function and surrounding context
cat -n lib/ecto_libsql/query.ex | head -100

Repository: ocean/ecto_libsql

Length of output: 3588


🏁 Script executed:

# Let's also check if there are any tests or documentation about the encode function
rg "def encode" -A 5 lib/

Repository: ocean/ecto_libsql

Length of output: 448


🏁 Script executed:

# Search for DBConnection documentation or any comments about the encode contract
rg "DBConnection\|encode.*params" lib/ecto_libsql/ -B 2 -A 2

Repository: ocean/ecto_libsql

Length of output: 43


🏁 Script executed:

# Check if there are any other database adapters or examples that show how params are handled
rg "Enum\.map.*params" lib/ -B 3 -A 3

Repository: ocean/ecto_libsql

Length of output: 1327


🏁 Script executed:

# Search for DBConnection.Query protocol definition or documentation
rg "DBConnection\.Query" lib/ --type elixir -B 2 -A 5

Repository: ocean/ecto_libsql

Length of output: 897


🏁 Script executed:

# Look for test files that might show how encode is called
find . -name "*test*.exs" -type f | head -5

Repository: ocean/ecto_libsql

Length of output: 210


🏁 Script executed:

# Check if there's any documentation about DBConnection contract
rg "encode.*_query.*params" . --type elixir -B 3 -A 3 | head -50

Repository: ocean/ecto_libsql

Length of output: 459


🏁 Script executed:

# Search for how params are passed to encode in this adapter
rg "DBConnection\." lib/ecto_libsql/ --type elixir -B 2 -A 2

Repository: ocean/ecto_libsql

Length of output: 990


🏁 Script executed:

# Look for the defimpl DBConnection.Query usage and see if there are type specs
ast-grep --pattern 'defimpl DBConnection.Query do
  $$$
end'

Repository: ocean/ecto_libsql

Length of output: 3323


🌐 Web query:

DBConnection.Query encode/3 callback signature params contract

💡 Result:

Signature/contract:

  • Callback: encode(query, params, opts) :: any(). This is called before execute to encode the params for the query. If it returns encoded params, those are used; if it raises DBConnection.EncodeError the query will be prepared again. [1][2][3]

Notes / common expectations:

  • Types are left as "any()" by DBConnection — the concrete encoded shape is adapter-specific (e.g. Postgres/Postgrex or MyXQL encode routines). Implementations typically return the params transformed into the adapter's expected format (or raise DBConnection.EncodeError on invalid input). [2][3]

Sources:

  1. DBConnection.Query (encode/3) docs. [hexdocs.pm/db_connection/DBConnection.Query.html]. [1]
  2. DBConnection EncodeError and callback discussion (behaviour examples). [hexdocs.pm/db_connection/DBConnection.EncodeError.html; blog/example]. [2]
  3. Adapter examples (Postgrex/MyXQL) showing encode usage/expectations. [postgrex docs; MyXQL example]. [3]

🌐 Web query:

Elixir DBConnection.Query protocol documentation encode

💡 Result:

The DBConnection.Query protocol defines encode/3 as:

  • Spec: encode(query, params, opts) :: any().
  • Purpose: encode parameters for a query before execution. If encode/3 raises DBConnection.EncodeError the query will be prepared again.
  • Related functions in the protocol: parse/2, describe/2, decode/3.

Reference documentation: DBConnection.Query (hexdocs). [1]
(DBConnection shows encode is invoked when preparing/executing and handles EncodeError by re-preparing.) [2]

Sources:
[1] DBConnection.Query — db_connection (protocol docs).
[2] DBConnection implementation showing encode usage and EncodeError handling (lib/db_connection.ex).


🏁 Script executed:

# Check if there are any guards used elsewhere in encode implementations
rg "def encode.*when" lib/

Repository: ocean/ecto_libsql

Length of output: 43


🏁 Script executed:

# Look for how params are used in the codebase - is it always passed as a list?
rg "encode\(" lib/ --type elixir -B 2 -A 2 | head -40

Repository: ocean/ecto_libsql

Length of output: 1395


🏁 Script executed:

# Check test files to see how encode is called
head -100 test/ecto_adapter_test.exs

Repository: ocean/ecto_libsql

Length of output: 3127


🏁 Script executed:

# Check how params flow through the system - search for places where encode is called or params are prepared
rg "execute\|prepare" lib/ecto_libsql/ --type elixir -B 2 -A 2 | head -60

Repository: ocean/ecto_libsql

Length of output: 43


🏁 Script executed:

# Look for DBConnection usage patterns to understand param handling
rg "DBConnection\." lib/ecto_libsql/connection.ex --type elixir -B 3 -A 3 2>/dev/null | head -80

Repository: ocean/ecto_libsql

Length of output: 43


🏁 Script executed:

# Check what Ecto adapters typically do - look at existing implementations
rg "def execute" lib/ --type elixir -B 2 -A 5 | head -60

Repository: ocean/ecto_libsql

Length of output: 3014


🏁 Script executed:

# Check the main connection execution flow to see how params reach encode
cat -n lib/ecto_libsql.ex | head -150

Repository: ocean/ecto_libsql

Length of output: 6261


🏁 Script executed:

# Search for where encode is invoked via DBConnection
rg "cast_params\|prepared\|DBConnection\." lib/ecto_libsql.ex --type elixir -B 2 -A 3 | head -80

Repository: ocean/ecto_libsql

Length of output: 43


🏁 Script executed:

# Look at similar Ecto adapters to understand if params guard is common practice
web_search: "Ecto adapter SQLite encode params guard DBConnection"

Repository: ocean/ecto_libsql

Length of output: 112


🏁 Script executed:

# Check if there are any existing nil or non-list handling patterns in the codebase
rg "is_list" lib/ --type elixir -B 1 -A 1

Repository: ocean/ecto_libsql

Length of output: 4534


🏁 Script executed:

# Examine how query execution happens - trace the flow from Ecto to encode
cat -n lib/ecto_libsql.ex | grep -A 20 "def execute"

Repository: ocean/ecto_libsql

Length of output: 43


🏁 Script executed:

# Let me trace through handle_execute to see how params flow to encode
cat -n lib/ecto_libsql.ex | sed -n '148,200p'

Repository: ocean/ecto_libsql

Length of output: 2210


🏁 Script executed:

# Check if DBConnection.Query protocol is documented in the repo or if there are type specs
rg "@spec encode" lib/ --type elixir -B 2 -A 2

Repository: ocean/ecto_libsql

Length of output: 43


🏁 Script executed:

# Check the complete flow - does DBConnection guarantee args is a list before calling encode?
rg "handle_execute\|def encode" lib/ecto_libsql.ex --type elixir -B 1 -A 10 | head -80

Repository: ocean/ecto_libsql

Length of output: 43


🏁 Script executed:

# Verify: are there any error cases or edge paths where encode could receive non-list?
rg "encode\(" lib/ecto_libsql.ex --type elixir -B 3 -A 3

Repository: ocean/ecto_libsql

Length of output: 43


🌐 Web query:

DBConnection Elixir encode callback receives list params or generic?

💡 Result:

Short answer: generic — the encode callback takes params() :: any(), not specifically a list. DBConnection passes whatever your adapter/query uses (lists, maps, tuples, etc.), and the DBConnection.Query.encode implementation for your adapter defines how they’re turned into iodata. [1][2]


🏁 Script executed:

# Final verification: confirm that the spelling issue exists at line 53
sed -n '53p' lib/ecto_libsql/query.ex

Repository: ocean/ecto_libsql

Length of output: 112


🏁 Script executed:

# Verify the proposed fix aligns with codebase patterns by checking similar encode-like functions
rg "def.*(_.*params.*_opts)" lib/ --type elixir -A 2 | head -40

Repository: ocean/ecto_libsql

Length of output: 224


Add guard to encode/3 to prevent crashes on non-list params.

The DBConnection.Query protocol specifies encode/3 receives params as any() type, not guaranteed to be a list. Enum.map/2 will raise if params is nil or a non-list, causing failures before the NIF layer. The codebase already uses is_list() guards elsewhere (e.g., normalise_args_for_query/2), confirming this edge case was anticipated.

Proposed fix
-    def encode(_query, params, _opts) do
-      Enum.map(params, &encode_param/1)
-    end
+    def encode(_query, params, _opts) when is_list(params) do
+      Enum.map(params, &encode_param/1)
+    end
+
+    def encode(_query, params, _opts), do: params
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Convert Elixir types to SQLite-compatible values before sending to NIF
def encode(_query, params, _opts) do
Enum.map(params, &encode_param/1)
end
# Convert Elixir types to SQLite-compatible values before sending to NIF
def encode(_query, params, _opts) when is_list(params) do
Enum.map(params, &encode_param/1)
end
def encode(_query, params, _opts), do: params
🤖 Prompt for AI Agents
In @lib/ecto_libsql/query.ex around lines 41 - 44, The encode/3 clause currently
assumes params is a list and calls Enum.map, which crashes for nil or non-list
params; add a guarded clause so def encode(_query, params, _opts) when
is_list(params) do Enum.map(params, &encode_param/1) end and provide a fallback
def encode(_query, params, _opts) when not is_list(params) do params end (or
otherwise return the unmodified params) to avoid raising before the NIF;
reference the encode/3 function and encode_param/1 helper.

Comment on lines +53 to +83
# 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tighten decode/3 row normalisation + fix en-AU spelling in comments.
Two points:

  1. If :rows comes back as a non-list (unexpected but possible given NIF/map boundaries), it currently passes through and may explode later when enumerated.
  2. Comments use “Normalize”; guidelines ask for British/Australian English (“Normalise”).
Proposed fix
-    # Normalize results for ecto_sql compatibility.
+    # Normalise 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 ->
+        rows == nil ->
           []
+        not is_list(rows) ->
+          []
         true ->
           rows
       end
🤖 Prompt for AI Agents
In @lib/ecto_libsql/query.ex around lines 53 - 83, The decode/3 function can let
non-list :rows pass through and later crash; update decode/3 (the clauses
handling columns, cmd, rows, num_rows) to coerce rows to a list when it's
non-nil and not a list (e.g. treat any non-list rows as [rows] or convert via
List.wrap semantics) before applying the existing cond that sets nil for write
commands with no RETURNING, and also change the comment header text from
"Normalize results" to British/Australian spelling "Normalise results" to match
guidelines.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant