Skip to content

Releases: ocean/ecto_libsql

0.9.1 - more SQL fixes, and an upstream libSQL announcement

11 May 23:53
26b6791

Choose a tag to compare

[0.9.1] - 2026-05-07

Fixed

  • Upsert ON CONFLICT DO UPDATE SQL Generation - Fixed two bugs that caused "near ?: syntax error" on any INSERT ... ON CONFLICT DO UPDATE query, breaking all upsert operations including Ash Framework's upsert? actions. (Thanks @AlanMcCann for PR #95!)
    • Missing :identifier expression handler: Ecto generates ON CONFLICT UPDATE clauses using {:identifier, _, ["column_name"]} fragment expressions. Without a matching expr/3 clause these fell through to the catch-all, producing invalid SQL such as SET "col" = EXCLUDED.?.
    • Bare ? parameter placeholders: SQLite requires numbered positional parameters (?1, ?2, …) when a statement contains multiple parameter groups (INSERT values + ON CONFLICT UPDATE). The adapter now uses numbered parameters throughout, consistent with the ecto_sqlite3 adapter.
    • IN clause with bound list parameters was also left using bare ? placeholders. The IN (?, ?, ?) handler now generates IN (?1, ?2, ?3) with correct start-index offsets, matching the numbering scheme used everywhere else.
    • Empty IN list edge case (where: field in ^[]) is handled explicitly with IN (SELECT NULL WHERE 1=0) since SQLite rejects IN ().
  • 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.

New Contributors

Full Changelog: 0.9.0...0.9.1

0.9.0 - Fixes, and now precompiled NIFs!

02 Feb 06:25
46760d9

Choose a tag to compare

[0.9.0] - 2026-02-02

Added

  • Precompiled NIF Binaries - Users no longer need Rust, cmake, or pkg-config installed. First compile drops from 5-10 minutes to seconds using prebuilt NIF downloads for 6 targets (2 macOS, 4 Linux). Force local compilation if required with ECTO_LIBSQL_BUILD=true. (Thanks @ricardo-valero for PR #70!)
  • GitHub Actions Release Workflow - Automated NIF builds on tag push for all supported targets using philss/rustler-precompiled-action, with version validation against mix.exs

Fixed

  • 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 for PR #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
    • 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

Changed

  • Dependency Updates - Bumped actions/checkout to v6, actions/upload-artifact to v6, updated Cargo and Credo dependencies

New Contributors

Full Changelog: 0.8.9...0.9.0

0.8.9 - Fixes are IN

02 Feb 01:58

Choose a tag to compare

0.8.9 - 2026-01-28

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.
  • SubQuery Support in IN Expressions - Fixed SubQuery expressions being incorrectly wrapped in JSON_EACH(), causing invalid SQL. Now properly generates inline subqueries like WHERE id IN (SELECT s0.id FROM table AS s0 WHERE ...). Fixes compatibility with libraries like Oban that use subqueries in UPDATE...WHERE patterns. (Thanks @nadilas for PR #66 !)
  • Ecto.Query.Tagged Expression Handling - Fixed type-cast fragments (e.g. type(fragment(...), :integer)) falling through to catch-all expression handler and generating incorrect parameter placeholders. Now properly handles %Ecto.Query.Tagged{} structs that Ecto's query planner creates from {:type, _, [expr, type]} AST nodes. Fixes parameter count mismatches with Hrana/Turso. (Thanks @nadilas for PR #67 !)

New Contributors

Full Changelog: 0.8.8...0.8.9

0.8.8 - Bug fixes: IN clause, RETURNING clause, not Santa Claus

23 Jan 02:43

Choose a tag to compare

Fixed

  • IN Clause Datatype Mismatch - Fixed issue #63 where IN clauses with parameterised lists caused datatype mismatch errors due to automatic JSON encoding of lists
  • SQL Comment Query Detection - Fixed Protocol.UndefinedError when queries start with SQL comments (both -- and /* */ styles) by properly skipping comments before detecting query type
  • RETURNING Clause for update_all/delete_all - Added RETURNING clause generation when using update_all/delete_all with select clauses, fixing Protocol.UndefinedError with Oban job fetching

Changed

  • Removed Unsupported Replication Tests - Removed replication integration tests that were testing unsupported features

Full Changelog: 0.8.7...0.8.8

0.8.7 - R*Tree spatial indexing, CHECK constraints, better DEFAULT and type handling

16 Jan 03:53

Choose a tag to compare

Added

  • CHECK Constraint Support - Column-level CHECK constraints in migrations
  • R*Tree Spatial Indexing - Full support for SQLite R*Tree virtual tables with 1D-5D indexing, validation, and comprehensive test coverage
  • ecto_sqlite3 Compatibility Test Suite - Comprehensive tests ensuring feature parity with ecto_sqlite3
  • Type Encoding Improvements - Automatic JSON encoding for plain maps, DateTime/Decimal parameter encoding, improved type coercion
  • Comprehensive Type Loader/Dumper Support - Full support for encoding/decoding temporal types (DateTime, NaiveDateTime, Date, Time), Decimal, and special nil values with proper ISO 8601 formatting
  • Default Value Type Handling - Support for Decimal, DateTime, NaiveDateTime, Date, Time, and :null as default values in migrations with warning logging for unsupported types
  • Connection Recovery Testing - Test suite for connection failure scenarios and recovery patterns
  • Query Encoding Improvements - Explicit test coverage for query parameter encoding with various data types and edge cases

Fixed

  • DateTime Microsecond Type Loading - Fixed :utc_datetime_usec, :naive_datetime_usec, and :time_usec loading from ISO 8601 strings with microsecond precision
  • Parameter Encoding - Automatic map-to-JSON conversion, DateTime/Decimal encoding for compatibility with Oban and other libraries
  • Migration Robustness - Handle :serial/:bigserial types, improved default value handling with warnings for unsupported types
  • JSON and RETURNING Clauses - Fixed JSON encoding in RETURNING queries and datetime function calls
  • Test Isolation - Comprehensive database cleanup across all test suites, per-test table clearing, improved resource management
  • DateTime Type Handling - Fixed datetime_decode to handle timezone-aware ISO 8601 strings and nil value encoding for date/time/bool types
  • Decimal Type Handling - Updated assertions to accept both numeric and string representations of decimal values in database queries
  • Datetime Roundtrip Preservation - Strengthened microsecond precision preservation in datetime round-trip tests

Changed

  • Test Suite Consolidation - Streamlined and improved test organisation with better coverage of edge cases, error handling, and concurrent operations
  • Documentation - Updated documentation with SQLite-specific query limitations, compatibility testing results, and guidance for type encoding edge cases

Full Changelog: 0.8.6...0.8.7

0.8.6 - JSON, UPSERT, EXPLAIN, and Rust fixes

07 Jan 07:53
caa255c

Choose a tag to compare

Release v0.8.6

Major Features

Named Parameters Execution Support

Full support for SQLite named parameter syntax (:name, @name, $name) in prepared statements and direct execution. Parameters are transparently converted from maps to positional arguments internally.

# Use named parameters in queries
{:ok, _, result, state} = EctoLibSql.handle_execute(
  "SELECT * FROM users WHERE email = :email AND status = :status",
  %{"email" => "alice@example.com", "status" => "active"},
  [],
  state
)

Works seamlessly with prepared statements, transactions, batch operations, and cursor streaming.

EXPLAIN QUERY PLAN Support

Full support for SQLite's EXPLAIN QUERY PLAN via Ecto's Repo.explain/2 and Repo.explain/3. Returns structured query plans for optimisation and debugging.

{:ok, plan} = Repo.explain(:all, from(u in User, where: u.active == true))
# Returns: [%{"id" => 2, "parent" => 0, "notused" => 0, "detail" => "SCAN users"}]

CTE (Common Table Expression) Support

Full support for SQL WITH clauses including recursive CTEs. Enables complex hierarchical queries and improved query organisation.

query = "hierarchy"
        |> with_cte("hierarchy", as: ^base_query)
        |> recursive_ctes(true)
        |> select([h], h.name)
Repo.all(query)

Query-Based UPSERT Support

Extended on_conflict support to handle query-based updates with keyword list syntax for dynamic operations.

Repo.insert(changeset,
  on_conflict: [set: [name: "updated", updated_at: DateTime.utc_now()]],
  conflict_target: [:email]
)

STRICT Table Option

Added support for SQLite's STRICT table option for stronger type enforcement at INSERT/UPDATE time.

create table(:users, options: [strict: true]) do
  add :name, :string
  add :age, :integer
end

Security Enhancements

CVE-2025-47736 Protection

Defence-in-depth measures against SQL injection via named parameters:

  • Comprehensive parameter validation to prevent atom table exhaustion
  • Improved parameter extraction to avoid malicious input exploitation
  • Validates all named parameters against statement introspection
  • Proper error handling for invalid or malicious parameter names

See SECURITY.md for full details.

Bug Fixes & Improvements

Statement Caching

  • Replaced unbounded persistent_term cache with bounded ETS LRU cache
  • Prevents memory leaks from unlimited prepared statement caching
  • Configurable cache size with automatic eviction

Error Handling

  • Propagate parameter introspection errors instead of silently falling back
  • Descriptive errors for invalid argument types
  • Improved error messages throughout

Code Quality

  • Fixed all Credo warnings
  • Improved test reliability and coverage
  • Better state threading and error handling
  • Removed redundant UTF-8 validation code

Documentation

  • Added generated/computed columns documentation
  • Enhanced JSON/JSONB function documentation
  • Comprehensive test coverage for all new features
  • Cross-connection security test suite

🔗 Resources


Full Changelog: 0.8.3...0.8.6

0.8.3 - SQLite extensions & fuzz testing

30 Dec 06:15

Choose a tag to compare

v0.8.3 Release Notes

New Features

RANDOM ROWID Support (libSQL Extension)

  • Generate pseudorandom row IDs instead of sequential integers for security/privacy
  • Prevents ID enumeration attacks and leaking business metrics
  • Usage: create table(:sessions, options: [random_rowid: true])

SQLite Extension Loading

  • Load SQLite extensions dynamically via enable_extensions/2 and load_ext/3
  • Supports FTS5, JSON1, R-Tree, PCRE, and custom extensions
  • Security-first: disabled by default, must be explicitly enabled

Enhanced Statement Introspection

  • stmt_parameter_name/3 - Get named parameter names (:name, @name, $name)
  • reset_stmt/2 - Explicitly reset statements for efficient reuse
  • get_stmt_columns/2 - Get full column metadata (name, origin, declared type)

Remote Encryption Support

  • New remote_encryption_key option for Turso encrypted databases
  • Works alongside existing local encryption_key for end-to-end encryption

Quality & Testing

  • Added Credo, Dialyxir, and Sobelow for comprehensive Elixir code analysis
  • Property-based fuzz testing with StreamData (SQL injection, transactions, edge cases)
  • Rust fuzz testing infrastructure with cargo-fuzz
  • Ported key tests from Ecto.Adapters.SQL for compatibility verification
  • Modernised Rust code: std::sync::LazyLock, stricter Clippy lints

Fixes

  • SQL injection prevention in Pragma module table name validation
  • Dialyzer type error in disconnect/2 spec
  • Improved fuzz test stability for savepoints and binary data

Changelog: https://github.com/ocean/ecto_libsql/blob/main/CHANGELOG.md
Full Changelog: 0.8.1...0.8.3

0.8.1 - Constraints bug fix

18 Dec 00:24

Choose a tag to compare

Fixed

  • Constraint Error Handling: Index Name Reconstruction (Issue #34)
  • Improved constraint name extraction to reconstruct full index names from SQLite error messages
  • Now follows Ecto's naming convention: table_column1_column2_index
  • Single-column constraints: "UNIQUE constraint failed: users.email" → "users_email_index" (previously just "email")
  • Multi-column constraints: "UNIQUE constraint failed: users.slug, users.parent_slug" → "users_slug_parent_slug_index"
  • Backtick handling: Properly strips trailing backticks appended by libSQL to error messages
  • Enhanced error messages: Preserves custom index names from enhanced format (index: custom_index_name)
  • NOT NULL constraints: Reconstructs index names following same convention
  • Enables accurate unique_constraint/3 and check_constraint/3 matching with custom index names in Ecto changesets

Full Changelog: 0.8.0...0.8.1

0.8.0 - Rust refactor

17 Dec 02:12

Choose a tag to compare

Overview

Major code refactoring and critical thread safety fixes with zero breaking changes.

Key Changes

Code Refactoring

  • Modularised Rust codebase: Split 2,302-line monolithic lib.rs into 13 focused modules (connection, query, batch, statement, transaction, savepoint, cursor, replication, metadata, utils, constants, models, decode)
  • Reorganised test suite: Refactored 1,194-line tests.rs into structured modules (integration, constants, utils)
  • Improved maintainability: Better code navigation and contributor onboarding with clearer separation of concerns
  • Zero behaviour changes: All APIs and functionality preserved

Thread Safety & Performance Fixes

  • Registry lock management: Fixed all functions to drop registry locks before async operations (prevents deadlocks)
  • Scheduler annotations: Added #[rustler::nif(schedule = "DirtyIo")] to blocking NIFs
  • Atom naming consistency: Fixed remote_primary → remote atom mismatch
  • Runtime optimisation: Use shared global TOKIO_RUNTIME instead of creating per-connection (prevents resource exhaustion)
  • Replication performance: Eliminated unnecessary async overhead for synchronous operations

Bug Fixes

  • Fixed prepared statement column introspection tests (enabled previously skipped tests)
  • Enhanced constraint error message handling with index name support
  • Improved remote Turso test stability
  • Better error handling for allocation failures
  • Proper SQL identifier quoting in PRAGMA queries

Note: This release is fully backward compatible. The refactoring is purely organisational with performance and stability improvements under the hood.

  • Rust refactoring, fix constraint error message handling by @ocean in #33

Full Changelog: 0.7.5...0.8.0

0.7.5 - query routing bug fix & performance improvements

15 Dec 07:28

Choose a tag to compare

Release 0.7.5 - Query Routing & Performance Improvements

This release focuses on critical bug fixes for batch operations and significant performance optimisations.

Fixed

Query/Execute Routing for Batch Operations

  • Implemented proper query() vs execute() routing in batch operations based on statement type
  • execute_batch() now automatically detects SELECT and RETURNING clauses to use the correct LibSQL method
  • execute_transactional_batch() applies the same intelligent routing logic for atomic operations
  • execute_batch_native() and execute_transactional_batch_native() now properly route SQL batch execution
  • Eliminates "Statement does not return data" errors for operations that should return rows
  • All operations with RETURNING clauses now correctly use the query() method

Performance: Batch Operation Optimisations

  • Eliminated per-statement argument clones in batch operations for better memory efficiency
  • Changed batch_stmts.iter() to batch_stmts.into_iter() to consume vectors by value
  • Removed unnecessary args.clone() calls in both transactional and non-transactional batches
  • Reduces memory allocations during batch execution for improved throughput

Lock Coupling Reduction

  • Dropped outer LibSQLConn mutex guard earlier in batch operations to reduce contention
  • Extract inner Arc<Mutexlibsql::Connection> before entering async blocks
  • Only hold inner connection lock during actual I/O operations
  • Applied to all four batch operation variants:
    • execute_batch()
    • execute_transactional_batch()
    • execute_batch_native()
    • execute_transactional_batch_native()
  • Reduces contention and deadlock surface area by following the established pattern from query_args()

Test Coverage & Documentation

  • Enhanced should_use_query() test coverage for block comment handling
  • Added explicit assertion documenting known limitation: RETURNING in block comments detected as false positive (safe behaviour)
  • Documented CTE and EXPLAIN detection limitations with clear scope notes
  • Added comprehensive future improvement recommendations with priority levels and implementation sketches
  • Added performance budget notes for optimisation efforts

Impact

  • Correctness: Batch operations with RETURNING clauses now work correctly
  • Performance: Reduced memory allocations and lock contention in batch operations
  • Reliability: Lower deadlock risk through improved lock coupling patterns
  • Maintainability: Better test coverage and documentation for edge cases

Full Changelog: 0.7.0...0.7.5