From 7d643d5242316a0b0d13067961ee7796f2d3b392 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Thu, 7 May 2026 12:44:44 +1000 Subject: [PATCH 1/5] chore(deps): update Elixir and Rust dependencies Elixir: - db_connection 2.9.0 -> 2.10.0 - ecto 3.13.5 -> 3.13.6 - jason 1.4.4 -> 1.4.5 Rust: - rustler 0.37.3 -> 0.37.4 - libc 0.2.185 -> 0.2.186 - cc 1.2.60 -> 1.2.61 - pin-project 1.1.11 -> 1.1.12 - js-sys 0.3.95 -> 0.3.97 - (plus transitive bumps) Also fix an OTP 27+ pattern-match warning in test/type_loader_dumper_test.exs: matching the literal 0.0 now only matches +0.0, so bind the zero rows and assert numeric equality instead (covers both +0.0 and -0.0 regardless of sort order). --- Cargo.lock | 60 +++++++++++++++++--------------- mix.lock | 6 ++-- test/type_loader_dumper_test.exs | 4 ++- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc1d5d8..f94fa49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,9 +245,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "shlex", @@ -827,10 +827,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -855,9 +857,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -1184,18 +1186,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" dependencies = [ "proc-macro2", "quote", @@ -1465,9 +1467,9 @@ dependencies = [ [[package]] name = "rustler" -version = "0.37.3" +version = "0.37.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c779e2cbfa2987990205d0d8fc142163739e45a4c6592dc637896c77fec01280" +checksum = "875c8fe88089b9bbc0977385e107d35bfae740c6b0734e60a1e9cc82d0017f49" dependencies = [ "inventory", "libloading 0.9.0", @@ -1477,9 +1479,9 @@ dependencies = [ [[package]] name = "rustler_codegen" -version = "0.37.3" +version = "0.37.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e120f8936c779b6c2e09992a2dfa9a4e8bcd0794c02bb654fde03e03ce8c31" +checksum = "afb5848e9c4cf3796f190d9b4516523af27f3444a3af1771f20465f6586d40b2" dependencies = [ "heck", "inventory", @@ -1526,9 +1528,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] @@ -1667,9 +1669,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -1733,7 +1735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", @@ -1761,9 +1763,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" dependencies = [ "bytes", "libc", @@ -2070,9 +2072,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -2083,9 +2085,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2093,9 +2095,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -2106,9 +2108,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] diff --git a/mix.lock b/mix.lock index 4c6c1b1..649467e 100644 --- a/mix.lock +++ b/mix.lock @@ -2,16 +2,16 @@ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "castore": {:hex, :castore, "1.0.17", "4f9770d2d45fbd91dcf6bd404cf64e7e58fed04fadda0923dc32acca0badffa2", [:mix], [], "hexpm", "12d24b9d80b910dd3953e165636d68f147a31db945d2dcb9365e441f8b5351e5"}, "credo": {:hex, :credo, "1.7.18", "5c5596bf7aedf9c8c227f13272ac499fe8eae6237bd326f2f07dfc173786f042", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a189d164685fd945809e862fe76a7420c4398fa288d76257662aecb909d6b3e5"}, - "db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"}, + "db_connection": {:hex, :db_connection, "2.10.0", "8ff756471e41765bd5563b633f73e9a94bbc138816e8644bb17d0d91bf260a95", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02cdd01b45efb1b550e68edbbea41be32de9b24bb07e1ea0e9cbc522ac377e54"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"}, + "ecto": {:hex, :ecto, "3.13.6", "352135b474f91d1ab99a1b502171d207e9db60421c9e3d0ecab4c7ab96b24d14", [:mix], [{:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8afa059bc16cd2c94739ec0a11e3e5df69d828125119109bef35f20a21a76af2"}, "ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"}, "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"}, "ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, - "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"}, diff --git a/test/type_loader_dumper_test.exs b/test/type_loader_dumper_test.exs index 315eeb7..cf9dd9c 100644 --- a/test/type_loader_dumper_test.exs +++ b/test/type_loader_dumper_test.exs @@ -346,7 +346,9 @@ defmodule EctoLibSql.TypeLoaderDumperTest do "SELECT float_field FROM all_types ORDER BY float_field" ) - assert [[-2.71828], [0.0], [0.0], [3.14]] = result.rows + assert [[-2.71828], [z1], [z2], [3.14]] = result.rows + assert z1 == 0.0 + assert z2 == 0.0 end test "handles special float values" do From 1c3bac78ef64842774244f8b9bb16429e8432567 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Thu, 7 May 2026 15:30:09 +1000 Subject: [PATCH 2/5] chore(security): ignore RUSTSEC-2026-0104 blocked on libsql upstream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rustls-webpki 0.102.x (via libsql → rustls 0.22 → hyper-rustls) has a reachable panic when parsing CRLs with an empty BIT STRING in the IssuingDistributionPoint extension (RUSTSEC-2026-0104). The fix requires rustls-webpki >=0.103.13, but libsql pins rustls 0.22.x which depends on the 0.102.x series — we cannot resolve this without an upstream bump. Added to cargo deny ignore list alongside the other rustls-webpki advisories (0049, 0098, 0099) that are blocked for the same reason. --- native/ecto_libsql/deny.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/native/ecto_libsql/deny.toml b/native/ecto_libsql/deny.toml index b6b9271..bb12454 100644 --- a/native/ecto_libsql/deny.toml +++ b/native/ecto_libsql/deny.toml @@ -85,6 +85,9 @@ ignore = [ # rustls-webpki 0.102.x wildcard DNS names bypass name constraints # Fix available in >=0.103.12 but libsql pins rustls 0.22.x which requires 0.102.x — waiting for upstream update { id = "RUSTSEC-2026-0099", reason = "Transitive dependency of libsql via rustls 0.22, waiting for upstream update" }, + # rustls-webpki 0.102.x reachable panic in CRL parsing via empty BIT STRING in IssuingDistributionPoint + # Fix available in >=0.103.13 but libsql pins rustls 0.22.x which requires 0.102.x — waiting for upstream update + { id = "RUSTSEC-2026-0104", reason = "Transitive dependency of libsql via rustls 0.22, waiting for upstream update" }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is false, then it uses a built-in git library. From 2183c88c3400fd323c065417eebd94ce5afa5e0f Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Thu, 7 May 2026 17:09:01 +1000 Subject: [PATCH 3/5] chore: prepare 0.9.1 release - Bump version to 0.9.1 - Add upstream status notice to README: Turso is transitioning away from libSQL toward their new Turso library; ecto_libsql will continue receiving bug fixes and security updates, likely transitioning to maintenance mode as the ecosystem matures - Update installation snippet to ~> 0.9.0 - Add CHANGELOG entry covering PRAGMA routing fix, dep updates, CI toolchain improvements, and security advisory acknowledgements --- CHANGELOG.md | 18 +++++++++- CLAUDE.md | 54 +++++++++++++++--------------- Cargo.lock | 2 +- README.md | 5 ++- RELEASE_PROCESS.md | 8 ++--- USAGE.md | 48 +++++++++++++------------- mix.exs | 2 +- native/ecto_libsql/Cargo.toml | 2 +- native/ecto_libsql/deny.toml | 8 ++--- native/ecto_libsql/fuzz/Cargo.lock | 2 +- native/ecto_libsql/src/utils.rs | 8 ++--- 11 files changed, 88 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4f7d80..36155cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ 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). +## [0.9.1] - 2026-05-07 + +### Fixed + +- **PRAGMA Statement Routing** - PRAGMA statements are now correctly routed through the `query()` path rather than the execute path, fixing incorrect behaviour when reading PRAGMA values (e.g. `PRAGMA journal_mode`, `PRAGMA synchronous`) via the Ecto adapter. + +### Changed + +- **Upstream Status Notice** - Added a note to the README and Hex documentation about Turso's transition away from libSQL toward their new Turso library (a full SQLite rewrite in Rust). `ecto_libsql` will continue to receive bug fixes and security updates; see the README for more context. +- **Dependency Updates** - Bumped `db_connection` 2.9.0 → 2.10.0, `ecto` 3.13.5 → 3.13.6, `jason` 1.4.4 → 1.4.5, `rustler` 0.37.3 → 0.37.4, `libsql` crates 0.9.29 → 0.9.30, plus various transitive Rust dependency updates. +- **CI Toolchain** - Replaced `erlef/setup-beam` with `mise` for Elixir/OTP version management; updated to current Elixir/OTP versions; applied `zizmor` GitHub Actions security hardening. + +### Security + +- Acknowledged three additional `rustls-webpki` 0.102.x advisories (RUSTSEC-2026-0049, RUSTSEC-2026-0098, RUSTSEC-2026-0099, RUSTSEC-2026-0104) in `cargo deny` - all are transitive via libsql's pinned `rustls 0.22.x` dependency and cannot be resolved until libsql updates upstream. + ## [0.9.0] - 2026-02-02 ### Added @@ -16,7 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **`Repo.exists?` Generates Valid SQL** - Fixed empty SELECT clause generating invalid SQL (`SELECT FROM "users"` instead of `SELECT 1 FROM "users"`), causing syntax errors. (Thanks [@ricardo-valero](https://github.com/ricardo-valero) for [PR #69](https://github.com/ocean/ecto_libsql/pull/69)!) - **NIF Cross-Compilation Workflow** - Fixed multiple issues preventing successful cross-compilation in GitHub Actions: - - Fixed Cargo workspace target directory mismatch — build output goes to the workspace root `target/` directory, not the crate subdirectory + - Fixed Cargo workspace target directory mismatch - build output goes to the workspace root `target/` directory, not the crate subdirectory - Moved `.cargo/config.toml` to workspace root so musl `-crt-static` rustflags are found when building from workspace root - Added `Cross.toml` for `RUSTLER_NIF_VERSION` environment passthrough to cross containers - Consolidated macOS runners to macos-15 (Apple Silicon) for both architectures diff --git a/CLAUDE.md b/CLAUDE.md index 54e6995..af9172b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,25 +14,25 @@ 1. Run formatters: `mix format && cd native/ecto_libsql && cargo fmt` 2. Verify checks pass: `mix format --check-formatted && cargo fmt --check` 3. **Only then** commit: `git commit -m "..."` -- **NEVER use `.unwrap()` in production Rust code** — use `safe_lock` helpers (see [Error Handling](#error-handling-patterns)) +- **NEVER use `.unwrap()` in production Rust code** - use `safe_lock` helpers (see [Error Handling](#error-handling-patterns)) - **Tests MAY use `.unwrap()`** for simplicity --- ## Landing the Plane (Session Completion) -**MANDATORY WORKFLOW — work is NOT complete until `git commit` succeeds:** +**MANDATORY WORKFLOW - work is NOT complete until `git commit` succeeds:** -1. **File issues for remaining work** — create Beads issues for anything needing follow-up -2. **Run quality gates** (if code changed) — tests, linters, builds -3. **Update issue status** — close finished work, update in-progress items +1. **File issues for remaining work** - create Beads issues for anything needing follow-up +2. **Run quality gates** (if code changed) - tests, linters, builds +3. **Update issue status** - close finished work, update in-progress items 4. **COMMIT**: ```bash git commit -m "Your commit message" bd sync ``` -5. **Clean up** — clear stashes, prune remote branches -6. **Verify** — all changes committed +5. **Clean up** - clear stashes, prune remote branches +6. **Verify** - all changes committed 7. **Hand off** - provide context for next session **If commit fails, resolve and retry until it succeeds.** @@ -71,11 +71,11 @@ Rust NIF (libsql-rs, connection registry, async runtime) ### Key Files **Elixir:** -- `lib/ecto_libsql.ex` — DBConnection protocol -- `lib/ecto_libsql/native.ex` — NIF wrappers -- `lib/ecto_libsql/state.ex` — Connection state -- `lib/ecto/adapters/libsql.ex` — Main adapter -- `lib/ecto/adapters/libsql/connection.ex` — SQL generation +- `lib/ecto_libsql.ex` - DBConnection protocol +- `lib/ecto_libsql/native.ex` - NIF wrappers +- `lib/ecto_libsql/state.ex` - Connection state +- `lib/ecto/adapters/libsql.ex` - Main adapter +- `lib/ecto/adapters/libsql/connection.ex` - SQL generation **Rust** (`native/ecto_libsql/src/`): @@ -98,8 +98,8 @@ Rust NIF (libsql-rs, connection registry, async runtime) | `tests/` | Test modules | **Tests:** -- `test/*.exs` — Elixir tests (adapter, integration, migrations, error handling, Turso) -- `native/ecto_libsql/src/tests/` — Rust tests (constants, utils, integration) +- `test/*.exs` - Elixir tests (adapter, integration, migrations, error handling, Turso) +- `native/ecto_libsql/src/tests/` - Rust tests (constants, utils, integration) ### Key Data Structures @@ -139,7 +139,7 @@ git checkout -b feature-descriptive-name # or bugfix-descriptive-name - **NEVER run `git clean`** without explicit user approval - **NEVER run `git checkout .`** or `git restore .` on the whole repo - **NEVER run `git reset --hard`** without explicit user approval -- Untracked files stay in place across branch switches — this is expected +- Untracked files stay in place across branch switches - this is expected ### PR Workflow @@ -158,7 +158,7 @@ git branch -d feature-descriptive-name ### Pre-Commit Checklist -**STRICT ORDER — do NOT skip steps or reorder:** +**STRICT ORDER - do NOT skip steps or reorder:** ```bash # 1. Format code (must come FIRST) @@ -221,11 +221,11 @@ git add . && git commit -m "..." ## Adding a New NIF Function -**Modern Rustler auto-detects all `#[rustler::nif]` functions — no manual registration needed.** +**Modern Rustler auto-detects all `#[rustler::nif]` functions - no manual registration needed.** -1. **Choose the right module** — connection lifecycle → `connection.rs`, query execution → `query.rs`, transactions → `transaction.rs`, batch → `batch.rs`, statements → `statement.rs`, cursors → `cursor.rs`, replication → `replication.rs`, metadata → `metadata.rs`, savepoints → `savepoint.rs` -2. **Define the Rust NIF** with `#[rustler::nif(schedule = "DirtyIo")]` — use `safe_lock` (never `.unwrap()`) — see [Error Handling](#error-handling-patterns) -3. **Add Elixir wrapper** in `lib/ecto_libsql/native.ex` — NIF stub + safe wrapper using `EctoLibSql.State` +1. **Choose the right module** - connection lifecycle → `connection.rs`, query execution → `query.rs`, transactions → `transaction.rs`, batch → `batch.rs`, statements → `statement.rs`, cursors → `cursor.rs`, replication → `replication.rs`, metadata → `metadata.rs`, savepoints → `savepoint.rs` +2. **Define the Rust NIF** with `#[rustler::nif(schedule = "DirtyIo")]` - use `safe_lock` (never `.unwrap()`) - see [Error Handling](#error-handling-patterns) +3. **Add Elixir wrapper** in `lib/ecto_libsql/native.ex` - NIF stub + safe wrapper using `EctoLibSql.State` 4. **Add tests** in both Rust (`native/ecto_libsql/src/tests/`) and Elixir (`test/`) 5. **Update documentation** in `USAGE.md` and `CHANGELOG.md` @@ -242,7 +242,7 @@ git add . && git commit -m "..." ### Rust Patterns (CRITICAL!) -**NEVER use `.unwrap()` in production code** — see `RUST_ERROR_HANDLING.md` for comprehensive patterns. +**NEVER use `.unwrap()` in production code** - see `RUST_ERROR_HANDLING.md` for comprehensive patterns. #### Pattern 1: Lock a Registry ```rust @@ -443,12 +443,12 @@ if entry.conn_id != conn_id { ### Internal Documentation -- **[USAGE.md](USAGE.md)** — API reference for library users -- **[README.md](README.md)** — User-facing documentation -- **[CHANGELOG.md](CHANGELOG.md)** — Version history -- **[ECTO_MIGRATION_GUIDE.md](ECTO_MIGRATION_GUIDE.md)** — Migrating from PostgreSQL/MySQL -- **[RUST_ERROR_HANDLING.md](RUST_ERROR_HANDLING.md)** — Error pattern reference -- **[TESTING.md](TESTING.md)** — Testing strategy and organisation +- **[USAGE.md](USAGE.md)** - API reference for library users +- **[README.md](README.md)** - User-facing documentation +- **[CHANGELOG.md](CHANGELOG.md)** - Version history +- **[ECTO_MIGRATION_GUIDE.md](ECTO_MIGRATION_GUIDE.md)** - Migrating from PostgreSQL/MySQL +- **[RUST_ERROR_HANDLING.md](RUST_ERROR_HANDLING.md)** - Error pattern reference +- **[TESTING.md](TESTING.md)** - Testing strategy and organisation ### External Documentation diff --git a/Cargo.lock b/Cargo.lock index f94fa49..62b9dbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,7 +357,7 @@ dependencies = [ [[package]] name = "ecto_libsql" -version = "0.9.0" +version = "0.9.1" dependencies = [ "bytes", "libsql", diff --git a/README.md b/README.md index 4d4d4ad..7b1e7a0 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ `ecto_libsql` is an (unofficial) Elixir [Ecto](https://github.com/elixir-ecto/ecto) database adapter for [LibSQL](https://github.com/tursodatabase/libsql) database files, and databases hosted on [Turso](https://turso.tech/), built with Rust NIFs. It supports local libSQL/SQLite files, remote replica with synchronisation, and remote only Turso databases. +> [!NOTE] +> **Upstream status:** [Turso is now closer to transitioning away from libSQL](https://turso.tech/blog/sync-benchmark) (their fork of SQLite) in favour of their new [Turso](https://turso.tech/turso) library - their full rewrite of SQLite in Rust. Activity on the [libSQL repository](https://github.com/tursodatabase/libsql) has slowed significantly as a result. We are monitoring the situation. `ecto_libsql` will continue to receive bug fixes and security updates, but is likely to transition to maintenance mode as the Elixir/Turso ecosystem matures. For those interested in using the new Turso library, [turso_ex](https://github.com/bytebottom/turso_ex) is an early-stage Elixir adapter currently under development. + ## Installation Add `ecto_libsql` to your dependencies in `mix.exs`: @@ -11,7 +14,7 @@ Add `ecto_libsql` to your dependencies in `mix.exs`: ```elixir def deps do [ - {:ecto_libsql, "~> 0.8.0"} + {:ecto_libsql, "~> 0.9.0"} ] end ``` diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index 97d17e9..029ad68 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -16,7 +16,7 @@ git push ## 2. Create the GitHub release (triggers CI) -The CI workflow triggers on tags matching `*.*.*`. The tag must match the version in `mix.exs` exactly — no `v` prefix, as the `base_url` in `native.ex` uses the raw version string. +The CI workflow triggers on tags matching `*.*.*`. The tag must match the version in `mix.exs` exactly - no `v` prefix, as the `base_url` in `native.ex` uses the raw version string. ```bash gh release create X.Y.Z --title "vX.Y.Z" --draft --generate-notes @@ -54,7 +54,7 @@ git commit -m "chore: update checksums for vX.Y.Z" git push ``` -This step is critical — the checksum file must be in the package so Hex.pm users can verify the downloaded NIFs. +This step is critical - the checksum file must be in the package so Hex.pm users can verify the downloaded NIFs. ## 6. Publish the GitHub release @@ -72,7 +72,7 @@ mix hex.publish ## Key Gotchas -- **Tag format**: Use `0.9.1` not `v0.9.1` — the `base_url` in `native.ex` is `releases/download/#{version}`, so the tag and version must match exactly. +- **Tag format**: Use `0.9.1` not `v0.9.1` - the `base_url` in `native.ex` is `releases/download/#{version}`, so the tag and version must match exactly. - **Checksum before publish**: Always regenerate and commit the checksum file *before* `mix hex.publish`. Without it, users get integrity errors when installing. -- **`--ignore-unavailable`**: Use with caution — only after confirming all 6 CI target builds have succeeded and all 6 release artefacts are present; otherwise it can mask missing binaries. +- **`--ignore-unavailable`**: Use with caution - only after confirming all 6 CI target builds have succeeded and all 6 release artefacts are present; otherwise it can mask missing binaries. - **Test run option**: The `workflow_dispatch` trigger on the release workflow has a `test_only` input that skips the `gh release upload` step, useful for testing the build matrix without creating a real release. diff --git a/USAGE.md b/USAGE.md index 0e4b378..a488de5 100644 --- a/USAGE.md +++ b/USAGE.md @@ -75,7 +75,7 @@ auth_token: System.get_env("TURSO_AUTH_TOKEN") ) -# Remote Replica (recommended for production — local reads, synced writes) +# Remote Replica (recommended for production - local reads, synced writes) {:ok, state} = EctoLibSql.connect( uri: "libsql://my-database.turso.io", auth_token: System.get_env("TURSO_AUTH_TOKEN"), @@ -167,7 +167,7 @@ Named constraints (`ON CONFLICT ON CONSTRAINT name`) are not supported. SQLite supports three named parameter syntaxes. Pass a map instead of a list: ```elixir -# :name, @name, or $name syntax — all equivalent. +# :name, @name, or $name syntax - all equivalent. {:ok, _, _, state} = EctoLibSql.handle_execute( "SELECT * FROM users WHERE email = :email AND status = :status", %{"email" => "alice@example.com", "status" => "active"}, @@ -186,16 +186,16 @@ Positional `?` parameters still work unchanged. Do not mix named and positional ### Prepared Statements -Cached after first preparation — ~10–15x faster for repeated queries. Bindings are cleared automatically between executions via `stmt.reset()`. +Cached after first preparation - ~10–15x faster for repeated queries. Bindings are cleared automatically between executions via `stmt.reset()`. ```elixir # Prepare and cache. {:ok, stmt_id} = EctoLibSql.Native.prepare(state, "SELECT * FROM users WHERE email = ?") -# Execute SELECT — returns rows. +# Execute SELECT - returns rows. {:ok, result} = EctoLibSql.Native.query_stmt(state, stmt_id, ["alice@example.com"]) -# Execute INSERT/UPDATE/DELETE — returns affected rows count. +# Execute INSERT/UPDATE/DELETE - returns affected rows count. # SQL must be re-supplied (used for replica sync detection). {:ok, num_rows} = EctoLibSql.Native.execute_stmt( state, stmt_id, @@ -226,10 +226,10 @@ statements = [ {"INSERT INTO users (name, email) VALUES (?, ?)", ["Bob", "bob@example.com"]}, ] -# Non-transactional — each executes independently; failures don't affect others. +# Non-transactional - each executes independently; failures don't affect others. {:ok, results} = EctoLibSql.Native.batch(state, statements) -# Transactional — all-or-nothing. +# Transactional - all-or-nothing. {:ok, results} = EctoLibSql.Native.batch_transactional(state, statements) # Raw SQL string (multiple statements separated by semicolons). @@ -239,7 +239,7 @@ statements = [ ### Cursor Streaming -For large result sets. `Repo.stream/2` is **not supported** — use `DBConnection.stream/4` instead: +For large result sets. `Repo.stream/2` is **not supported** - use `DBConnection.stream/4` instead: ```elixir stream = DBConnection.stream( @@ -290,7 +290,7 @@ R*Tree is a SQLite virtual table for efficient multidimensional range queries (g - Not compatible with `:strict`, `:random_rowid`, or `:without_rowid` ```elixir -# In a migration — Ecto's default id column + 2D bounds = 5 columns (odd ✓). +# In a migration - Ecto's default id column + 2D bounds = 5 columns (odd ✓). create table(:geo_regions, options: [rtree: true]) do add :min_lat, :float add :max_lat, :float @@ -366,7 +366,7 @@ If using `primary_key: false`, add an explicit `add :id, :integer, primary_key: ) ``` -Encryption key must be at least 32 characters. Use environment variables or a secret manager — never hard-code keys. +Encryption key must be at least 32 characters. Use environment variables or a secret manager - never hard-code keys. ### JSON Helpers @@ -404,7 +404,7 @@ fragment = JSON.arrow_fragment("col", "key", :double_arrow) # "col ->> | `set` | ✅ | ✅ | Use JSON paths (`$.key`) | | `replace` | ❌ | ✅ | Use JSON paths (`$.key`) | | `insert` | ✅ | ❌ | Use JSON paths (`$.key`) | -| `patch` | ✅ | ✅ | RFC 7396 — top-level object keys only; set key to `null` to remove | +| `patch` | ✅ | ✅ | RFC 7396 - top-level object keys only; set key to `null` to remove | JSONB binary format is ~5–10% smaller and faster to process. All JSON functions accept both text and JSONB transparently. @@ -415,13 +415,13 @@ JSONB binary format is ~5–10% smaller and faster to process. All JSON function ### Configuration ```elixir -# config/dev.exs — local +# config/dev.exs - local config :my_app, MyApp.Repo, adapter: Ecto.Adapters.LibSql, database: "my_app_dev.db", pool_size: 5 -# config/runtime.exs — production (remote replica recommended) +# config/runtime.exs - production (remote replica recommended) if config_env() == :prod do config :my_app, MyApp.Repo, adapter: Ecto.Adapters.LibSql, @@ -512,7 +512,7 @@ alter table(:users) do end ``` -Changes apply only to **new or updated rows** — existing data is not revalidated. Not available in standard SQLite. +Changes apply only to **new or updated rows** - existing data is not revalidated. Not available in standard SQLite. #### Generated / Computed Columns (SQLite 3.31+) @@ -532,7 +532,7 @@ Constraints: cannot have DEFAULT values, cannot be PRIMARY KEY, expression must ```elixir # mix phx.new my_app --database libsqlex -# Or add to existing project — config as shown above. +# Or add to existing project - config as shown above. # Standard Phoenix context pattern works unchanged. defmodule MyApp.Accounts do @@ -570,7 +570,7 @@ The following Elixir types are **automatically encoded** when passed as top-leve **⚠️ Nested structures are NOT automatically encoded:** ```elixir -# ❌ Fails — DateTime inside map is not auto-encoded. +# ❌ Fails - DateTime inside map is not auto-encoded. SQL.query!(Repo, "INSERT INTO events (metadata) VALUES (?)", [ %{"created_at" => DateTime.utc_now(), "data" => "value"} ]) @@ -584,11 +584,11 @@ Third-party date types (e.g. `Timex.DateTime`) must be converted to standard Eli ### Limitations and Known Issues -#### `freeze_replica/1` — Not Supported +#### `freeze_replica/1` - Not Supported `EctoLibSql.Native.freeze_replica/1` returns `{:error, :unsupported}`. Workaround: copy the replica `.db` file and configure your app to use it directly. -#### `Repo.stream/2` — Not Supported +#### `Repo.stream/2` - Not Supported Use `DBConnection.stream/4` with `max_rows:` instead (see [Cursor Streaming](#cursor-streaming)). @@ -602,9 +602,9 @@ The following Ecto query features do not work due to SQLite limitations: | `exists()` with `parent_as()` | Complex nested query correlation unsupported | | `fragment(literal(...))` / `fragment(identifier(...))` | Not supported in SQLite fragments | | `ago(N, unit)` | Does not work with TEXT-based timestamps | -| `{:array, _}` type | Not supported — use JSON or separate tables | +| `{:array, _}` type | Not supported - use JSON or separate tables | | Mixed arithmetic (string + float) | SQLite returns TEXT instead of coercing to REAL | -| Case-insensitive text comparison | TEXT is case-sensitive by default — use `COLLATE NOCASE` | +| Case-insensitive text comparison | TEXT is case-sensitive by default - use `COLLATE NOCASE` | **Compatibility summary: ~74% of Ecto features pass (31/42 tests). All failures are SQLite limitations, not adapter bugs.** @@ -624,7 +624,7 @@ The following Ecto query features do not work due to SQLite limitations: | `:naive_datetime` / `:utc_datetime` | `DATETIME` | ✅ ISO8601 | | `:*_usec` variants | `DATETIME` | ✅ ISO8601 with microseconds | | `:map` / `:json` | `TEXT` | ✅ JSON string | -| `{:array, _}` | — | ❌ Not supported | +| `{:array, _}` | - | ❌ Not supported | Use `@timestamps_opts [type: :utc_datetime_usec]` on schemas requiring microsecond precision. @@ -733,8 +733,8 @@ For standard SQLite (without libSQL's `ALTER COLUMN`), use table recreation: cre | Function | Signature | Returns | |----------|-----------|---------| | `EctoLibSql.Native.vector/1` | `(list_of_numbers)` | `String.t()` | -| `EctoLibSql.Native.vector_type/2` | `(dimensions, :f32 \| :f64)` | `String.t()` — e.g. `"F32_BLOB(1536)"` | -| `EctoLibSql.Native.vector_distance_cos/2` | `(column, vector)` | `String.t()` — SQL expression | +| `EctoLibSql.Native.vector_type/2` | `(dimensions, :f32 \| :f64)` | `String.t()` - e.g. `"F32_BLOB(1536)"` | +| `EctoLibSql.Native.vector_distance_cos/2` | `(column, vector)` | `String.t()` - SQL expression | ### Connection Utilities @@ -795,7 +795,7 @@ Verify credentials with `turso db show ` and `turso db tokens create =0.103.12 but libsql pins rustls 0.22.x which requires 0.102.x — waiting for upstream update + # Fix available in >=0.103.12 but libsql pins rustls 0.22.x which requires 0.102.x - waiting for upstream update { id = "RUSTSEC-2026-0098", reason = "Transitive dependency of libsql via rustls 0.22, waiting for upstream update" }, # rustls-webpki 0.102.x wildcard DNS names bypass name constraints - # Fix available in >=0.103.12 but libsql pins rustls 0.22.x which requires 0.102.x — waiting for upstream update + # Fix available in >=0.103.12 but libsql pins rustls 0.22.x which requires 0.102.x - waiting for upstream update { id = "RUSTSEC-2026-0099", reason = "Transitive dependency of libsql via rustls 0.22, waiting for upstream update" }, # rustls-webpki 0.102.x reachable panic in CRL parsing via empty BIT STRING in IssuingDistributionPoint - # Fix available in >=0.103.13 but libsql pins rustls 0.22.x which requires 0.102.x — waiting for upstream update + # Fix available in >=0.103.13 but libsql pins rustls 0.22.x which requires 0.102.x - waiting for upstream update { id = "RUSTSEC-2026-0104", reason = "Transitive dependency of libsql via rustls 0.22, waiting for upstream update" }, ] # If this is true, then cargo deny will use the git executable to fetch advisory database. diff --git a/native/ecto_libsql/fuzz/Cargo.lock b/native/ecto_libsql/fuzz/Cargo.lock index 775ffab..8ddad03 100644 --- a/native/ecto_libsql/fuzz/Cargo.lock +++ b/native/ecto_libsql/fuzz/Cargo.lock @@ -364,7 +364,7 @@ dependencies = [ [[package]] name = "ecto_libsql" -version = "0.9.0" +version = "0.9.1" dependencies = [ "bytes", "libsql", diff --git a/native/ecto_libsql/src/utils.rs b/native/ecto_libsql/src/utils.rs index 7213a52..85ff409 100644 --- a/native/ecto_libsql/src/utils.rs +++ b/native/ecto_libsql/src/utils.rs @@ -363,10 +363,10 @@ fn skip_whitespace_and_comments(bytes: &[u8]) -> usize { /// Returns true if the statement should use `query()` rather than `execute()`. /// /// Routes through `query()` when the statement may return rows: -/// - `SELECT` — always returns rows -/// - `WITH` — CTE; typically precedes a `SELECT` -/// - `EXPLAIN` — always returns rows -/// - `PRAGMA` — may return rows (e.g. `PRAGMA wal_checkpoint(FULL)`) +/// - `SELECT` - always returns rows +/// - `WITH` - CTE; typically precedes a `SELECT` +/// - `EXPLAIN` - always returns rows +/// - `PRAGMA` - may return rows (e.g. `PRAGMA wal_checkpoint(FULL)`) /// - Any statement containing a `RETURNING` clause /// /// Performance optimisations: From aaa4db895298854e11ac75d079e07bb46ad79e92 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Sun, 10 May 2026 22:29:57 +1200 Subject: [PATCH 4/5] docs: Clarify patch behaviour --- USAGE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/USAGE.md b/USAGE.md index a488de5..ea72788 100644 --- a/USAGE.md +++ b/USAGE.md @@ -404,7 +404,7 @@ fragment = JSON.arrow_fragment("col", "key", :double_arrow) # "col ->> | `set` | ✅ | ✅ | Use JSON paths (`$.key`) | | `replace` | ❌ | ✅ | Use JSON paths (`$.key`) | | `insert` | ✅ | ❌ | Use JSON paths (`$.key`) | -| `patch` | ✅ | ✅ | RFC 7396 - top-level object keys only; set key to `null` to remove | +| `patch` | ✅ | ✅ | RFC 7396 merge patch for objects (recursive); set key to `null` to remove; arrays are replaced as whole values | JSONB binary format is ~5–10% smaller and faster to process. All JSON functions accept both text and JSONB transparently. From 74a1af27cff71d492d722549c95fde6569e4a6c5 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Sun, 10 May 2026 22:30:26 +1200 Subject: [PATCH 5/5] ci: Improve error handling in tests --- test/ecto_integration_test.exs | 2 +- test/ecto_migration_test.exs | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/test/ecto_integration_test.exs b/test/ecto_integration_test.exs index a1da690..56d4eab 100644 --- a/test/ecto_integration_test.exs +++ b/test/ecto_integration_test.exs @@ -61,7 +61,7 @@ defmodule Ecto.Integration.EctoLibSqlTest do setup_all do # Start the test repo - {:ok, _} = TestRepo.start_link(database: @test_db) + {:ok, _} = TestRepo.start_link(database: @test_db, pool_size: 1) # Create tables Ecto.Adapters.SQL.query!(TestRepo, """ diff --git a/test/ecto_migration_test.exs b/test/ecto_migration_test.exs index 76406ba..26fb4d6 100644 --- a/test/ecto_migration_test.exs +++ b/test/ecto_migration_test.exs @@ -980,12 +980,19 @@ defmodule Ecto.Adapters.LibSql.MigrationTest do table = %Table{name: :users, prefix: nil} columns = [{:add, :status, :string, [default: :unknown]}] - # Should not raise FunctionClauseError. - [sql] = Connection.execute_ddl({:create, table, columns}) + import ExUnit.CaptureLog + + log = + capture_log(fn -> + # Should not raise FunctionClauseError. + [sql] = Connection.execute_ddl({:create, table, columns}) + + # Unexpected atom should be treated as no default. + assert sql =~ ~r/"status".*TEXT/ + refute sql =~ ~r/"status".*DEFAULT/ + end) - # Unexpected atom should be treated as no default. - assert sql =~ ~r/"status".*TEXT/ - refute sql =~ ~r/"status".*DEFAULT/ + assert log =~ "Unsupported default value type in migration: :unknown" end test "handles map defaults (JSON encoding)" do