From 8b9420882a21ba39b0d2ce598449c1b34c93fa8c Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Tue, 27 Jan 2026 17:52:24 +1100 Subject: [PATCH 1/3] test: add failing test case for ~w() sigil in IN clause (issue #63) Amp-Thread-ID: https://ampcode.com/threads/T-019bfe38-44f9-74fc-b369-9b0ab4ee93e8 Co-authored-by: Amp --- test/issue_63_in_clause_test.exs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/issue_63_in_clause_test.exs b/test/issue_63_in_clause_test.exs index 1298dbe..5651f82 100644 --- a/test/issue_63_in_clause_test.exs +++ b/test/issue_63_in_clause_test.exs @@ -121,4 +121,32 @@ defmodule EctoLibSql.Issue63InClauseTest do # Empty IN clause should match nothing assert result == [] end + + test "IN clause with literal word list (~w) - e.g. oban" do + # test data matching Oban's state values + Ecto.Adapters.SQL.query!(TestRepo, """ + INSERT INTO test_items (state, name, inserted_at, updated_at) + VALUES ('scheduled', 'job1', datetime('now'), datetime('now')), + ('retryable', 'job2', datetime('now'), datetime('now')), + ('available', 'job3', datetime('now'), datetime('now')), + ('completed', 'job4', datetime('now'), datetime('now')) + """) + + # This is how Oban's Lite engine queries jobs - using ~w() sigil + # ~w() creates a compile-time list that Ecto wraps in %Ecto.Query.Tagged{} + # This was causing "datatype mismatch" because the Tagged struct wasn't + # being handled by the IN clause expression generator + query = + from(t in "test_items", + where: t.state in ~w(scheduled retryable), + select: t.name + ) + + # should not raise "datatype mismatch" error + result = TestRepo.all(query) + + assert length(result) == 2 + assert "job1" in result + assert "job2" in result + end end From 3a3e977b2b89f18e03a4f8b02e3008f443942ad2 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Tue, 27 Jan 2026 17:55:44 +1100 Subject: [PATCH 2/3] fix: handle Ecto.Query.Tagged structs in IN clauses (e.g. ~w() sigil from oban) - Add catch-all expr clause for IN with non-list right side - Extract list values from Ecto.Query.Tagged structs before generating IN clause - Fall back to JSON_EACH for pre-encoded JSON arrays - Fixes issue #63: Oban job state queries using ~w() sigil now work correctly Amp-Thread-ID: https://ampcode.com/threads/T-019bfe38-44f9-74fc-b369-9b0ab4ee93e8 Co-authored-by: Amp --- .claude/settings.local.json | 4 +++- lib/ecto/adapters/libsql/connection.ex | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 77f11fd..de6cc8e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -53,7 +53,9 @@ "Bash(gh pr diff:*)", "Bash(gh pr checks:*)", "Bash(gh run view:*)", - "Bash(gh pr checkout:*)" + "Bash(gh pr checkout:*)", + "mcp__acp__Bash", + "mcp__acp__Edit" ], "deny": [], "ask": [] diff --git a/lib/ecto/adapters/libsql/connection.ex b/lib/ecto/adapters/libsql/connection.ex index e72af65..1d296ee 100644 --- a/lib/ecto/adapters/libsql/connection.ex +++ b/lib/ecto/adapters/libsql/connection.ex @@ -1249,6 +1249,27 @@ defmodule Ecto.Adapters.LibSql.Connection do [expr(left, sources, query), " IN (", args, ?)] end + # Catch-all for IN with non-list right side (e.g. Ecto.Query.Tagged from ~w() sigil, JSON-encoded arrays) + # This handles cases where the right side has been pre-processed or wrapped by Ecto + defp expr({:in, _, [left, right]}, sources, query) do + case right do + %Ecto.Query.Tagged{value: val} when is_list(val) -> + # Extract list from Tagged struct and generate proper IN clause + args = Enum.map_intersperse(val, ?,, &expr(&1, sources, query)) + [expr(left, sources, query), " IN (", args, ?)] + + _ -> + # Default fallback: use JSON_EACH to handle JSON-encoded arrays or other complex types + [ + expr(left, sources, query), + " IN (SELECT value FROM JSON_EACH(", + expr(right, sources, query), + ?), + ?) + ] + end + end + # LIKE defp expr({:like, _, [left, right]}, sources, query) do [expr(left, sources, query), " LIKE ", expr(right, sources, query)] From 7d43878216755cd785f5bd17e00cc8c1ce86ca76 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Tue, 27 Jan 2026 17:58:52 +1100 Subject: [PATCH 3/3] docs: add unreleased changelog entry for issue #63 fix Amp-Thread-ID: https://ampcode.com/threads/T-019bfe38-44f9-74fc-b369-9b0ab4ee93e8 Co-authored-by: Amp --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19bc611..106a114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- **IN Clause with Ecto.Query.Tagged Structs** - Fixed issue #63 where `~w()` sigil word lists in IN clauses returned zero results due to Tagged struct wrapping. Now properly extracts list values from `Ecto.Query.Tagged` structs before generating IN clauses, enabling these patterns to work correctly. + ## [0.8.8] - 2026-01-23 ### Fixed