All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[0.8.8] - 2026-01-23

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

[0.8.7] - 2026-01-16

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 organization with better coverage of edge cases, error handling, and concurrent operations
  • Code Quality - Fixed Credo warnings, improved error handling patterns, removed unused variables/imports, enhanced British English consistency
  • Documentation - Updated documentation with SQLite-specific query limitations, compatibility testing results, and guidance for type encoding edge cases

[0.8.6] - 2026-01-07

Added

  • R*Tree Spatial Indexing Support

    • Full support for SQLite R*Tree virtual tables for multidimensional spatial indexing
    • Table creation: Use options: [rtree: true] in Ecto migrations
    • Dimensions supported: 1D to 5D (3 to 11 columns total including ID)
    • Column structure: First column must be id (integer primary key), followed by min/max coordinate pairs
    • Validation: Automatic validation of column count (odd numbers only), first-column requirements (must be 'id'), dimensional constraints, and incompatible table options - virtual tables reject standard table options (:strict, :random_rowid, :without_rowid) with clear error messages
    • Use cases: Geographic bounding boxes, collision detection, time-range queries, spatial indexing
    • Migration example:
      create table(:geo_regions, options: [rtree: true]) do
        add :min_lat, :float
        add :max_lat, :float
        add :min_lng, :float
        add :max_lng, :float
      end
    • Query patterns: Point containment, bounding box intersection, range queries
    • Virtual table syntax: Generates CREATE VIRTUAL TABLE ... USING rtree(...) DDL
    • Implementation: New create_rtree_table/3, validate_rtree_options!/1, and validate_rtree_columns!/1 helpers in connection.ex
    • Comprehensive test coverage in test/rtree_test.exs covering 2D/3D tables, validation, queries, and CRUD operations
    • Documentation: Full guide in USAGE.md with examples for geographic data, time-series, and hybrid vector+spatial search
    • Comparison guide: R*Tree vs Vector Search decision matrix in documentation
    • Ecto integration: Works with Ecto schemas using fragments for spatial queries
  • Named Parameters Execution Support

    • Full support for SQLite named parameter syntax in prepared statements and direct execution
    • Three SQLite syntaxes supported: :name, @name, $name
    • Transparent conversion: Map-based named parameters automatically converted to positional arguments for internal execution
    • Use cases: Dynamic query building, parameter validation, better debuggability, API introspection
    • Execution paths: Works with prepared statements, transactions, batch operations, and cursor streaming
    • Backward compatibility: Existing positional parameter syntax (?) continues to work unchanged
    • Implementation: Automatic parameter binding detection and conversion in both transactional and non-transactional paths
    • Usage examples:
      # Named parameters in prepared statements
      {:ok, stmt_id} = EctoLibSql.Native.prepare(
        state,
        "SELECT * FROM users WHERE email = :email AND status = :status"
      )
      
      # Execute with named parameters as map
      {:ok, result} = EctoLibSql.Native.query_stmt(
        state,
        stmt_id,
        %{"email" => "alice@example.com", "status" => "active"}
      )
      
      # Alternative syntaxes
      "SELECT * FROM users WHERE email = @email"
      "SELECT * FROM users WHERE email = $email"
      
      # Works with direct execution
      {:ok, _, result, state} = EctoLibSql.handle_execute(
        "INSERT INTO users (name, email) VALUES (:name, :email)",
        %{"name" => "Alice", "email" => "alice@example.com"},
        [],
        state
      )
      
      # Works with transactions
      {:ok, :begin, state} = EctoLibSql.handle_begin([], state)
      {:ok, _, _, state} = EctoLibSql.handle_execute(
        "UPDATE users SET status = :status WHERE id = :id",
        %{"status" => "inactive", "id" => 123},
        [],
        state
      )
      {:ok, _, state} = EctoLibSql.handle_commit([], state)
    • Type handling: All value types (strings, integers, floats, binaries, nil) properly converted
    • Parameter validation: Uses stmt_parameter_name/3 introspection for validation
    • Edge cases handled: Empty parameter maps, missing parameters with proper error messages, mixed positional and named parameters
    • Added comprehensive test coverage in test/named_parameters_execution_test.exs covering all SQLite syntaxes, CRUD operations, transactions, batch operations, and backward compatibility
  • Query-Based UPSERT Support (on_conflict with Ecto.Query)

    • Extended on_conflict support to handle query-based updates
    • Allows using keyword list syntax for dynamic update operations:
      Repo.insert(changeset,
        on_conflict: [set: [name: "updated", updated_at: DateTime.utc_now()]],
        conflict_target: [:email]
      )
    • Supports :set and :inc operations in the update clause
    • Generates proper ON CONFLICT (...) DO UPDATE SET ... SQL
    • Requires explicit :conflict_target (LibSQL/SQLite requirement)
    • Implementation in connection.ex:594-601 with update_all_for_on_conflict/1 helper
    • 3 new tests covering query-based on_conflict with set, inc, and error cases
  • CTE (Common Table Expression) Support

    • Full support for SQL WITH clauses in Ecto queries
    • Both simple and recursive CTEs supported
    • SQL generation in Ecto.Adapters.LibSql.Connection (connection.ex:843-883)
    • Rust NIF support for CTE detection in utils.rs:should_use_query()
    • CTEs treated as SELECT-like queries (return rows) for proper query/execute routing
    • Example usage:
      cte_query = from(e in Employee, where: e.level >= 2, select: %{id: e.id, name: e.name})
      query = "high_level_employees"
              |> with_cte("high_level_employees", as: ^cte_query)
              |> select([h], h.name)
      Repo.all(query)
    • Recursive CTE example:
      query = "hierarchy"
              |> with_cte("hierarchy", as: ^base_query)
              |> recursive_ctes(true)
              |> select([h], h.name)
    • 9 new CTE tests covering simple, recursive, and edge cases
  • EXPLAIN QUERY PLAN Support

    • Full support for SQLite's EXPLAIN QUERY PLAN via Ecto's Repo.explain/2 and Repo.explain/3
    • Query detection: Rust NIF should_use_query() now detects EXPLAIN statements for proper query/execute routing
    • Ecto.Multi compatibility: explain_query/4 callback returns {:ok, maps} tuple format required by Ecto.Multi
    • Output format: Returns list of maps with id, parent, notused, and detail keys matching SQLite's output
    • Usage examples:
      # Basic EXPLAIN QUERY PLAN
      {:ok, plan} = Repo.explain(:all, from(u in User, where: u.active == true))
      # Returns: [%{"id" => 2, "parent" => 0, "notused" => 0, "detail" => "SCAN users"}]
      
      # With options
      {:ok, plan} = Repo.explain(:all, query, analyze: true)
      
      # Direct SQL execution
      {:ok, _, result, _state} = EctoLibSql.handle_execute(
        "EXPLAIN QUERY PLAN SELECT * FROM users WHERE id = ?",
        [1],
        [],
        state
      )
    • Implementation: Query detection in utils.rs:should_use_query(), SQL generation in connection.ex:explain_query/4
    • Test coverage: 12 tests across explain_simple_test.exs and explain_query_test.exs
  • STRICT Table Option Support

    • Added support for SQLite's STRICT table option for stronger type enforcement
    • Usage: Pass options: [strict: true] to create table() in migrations
    • Example:
      create table(:users, options: [strict: true]) do
        add :name, :string
        add :age, :integer
      end
    • STRICT tables enforce column type constraints at INSERT/UPDATE time
    • Helps catch type errors early and ensures data integrity
    • Can be combined with other table options
  • Enhanced JSON and JSONB Functions

    • Added comprehensive JSON manipulation functions for working with JSON data
    • SQL injection protection with proper parameter handling
    • Functions include json_extract/2, json_type/2, json_valid/1, and more
    • Consolidated JSON result handling for consistent behaviour
    • Extensive test coverage for all JSON operations
  • Cross-Connection Security Tests

    • Added comprehensive tests for transaction isolation across connections
    • Validates that transactions from one connection cannot be accessed by another
    • Tests cover savepoints, prepared statements, and cursors
    • Ensures strict connection ownership and prevents security vulnerabilities
  • Generated/Computed Columns Documentation

    • Added documentation for SQLite's generated column support
    • Covers both VIRTUAL and STORED generated columns
    • Examples of computed columns in migrations

Security

  • CVE-2025-47736 Protection
    • 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 documentation for details

Fixed

  • Statement Caching Improvements

    • Replaced unbounded persistent_term cache with bounded ETS LRU cache
    • Prevents memory leaks from unlimited prepared statement caching
    • Configurable cache size with automatic eviction of least-recently-used entries
    • Improved cache performance and memory footprint
  • Error Handling Improvements

    • Propagate parameter introspection errors instead of silently falling back
    • Return descriptive errors for invalid argument types in parameter normalisation
    • Improved error tuple handling in fuzz tests
    • Better error messages throughout the codebase
  • Code Quality Improvements

    • Fixed Credo warnings (nesting, unused variables, assertions)
    • Standardised unused variable naming for consistency
    • Improved test reliability and reduced flakiness
    • Better state threading in security tests
    • Fixed binary blob round-trip handling in tests

Changed

  • Rust UTF-8 Validation Cleanup
    • Removed redundant UTF-8 validation comments and tautological boundary checks
    • Removed redundant validate_utf8_sql function (SQLite already validates UTF-8)
    • Cleaner, more maintainable codebase

[0.8.3] - 2025-12-29

Added

  • RANDOM ROWID Support (libSQL Extension)

    • Added support for libSQL's RANDOM ROWID table option to generate pseudorandom rowid values instead of consecutive integers
    • Security/Privacy Benefits: Prevents ID enumeration attacks and leaking business metrics through sequential IDs
    • Usage: Pass options: [random_rowid: true] to create table() in migrations
    • Example:
      create table(:sessions, options: [random_rowid: true]) do
        add :token, :string
        add :user_id, :integer
        timestamps()
      end
    • Compatibility: Works with all table configurations (single PK, composite PK, IF NOT EXISTS)
    • Restrictions: Mutually exclusive with WITHOUT ROWID and AUTOINCREMENT (per libSQL specification)
    • Validation: Early validation of mutually exclusive options with clear error messages (connection.ex:386-407)
      • Raises ArgumentError if RANDOM ROWID is combined with WITHOUT ROWID
      • Raises ArgumentError if RANDOM ROWID is combined with AUTOINCREMENT on any column
      • Prevents libSQL runtime errors by catching conflicts during migration compilation
    • SQL output: CREATE TABLE sessions (...) RANDOM ROWID
    • Added 7 comprehensive tests covering RANDOM ROWID with various configurations and validation scenarios
    • Documentation: See libSQL extensions guide
  • SQLite Extension Loading Support (enable_extensions/2, load_ext/3)

    • Load SQLite extensions dynamically from shared library files
    • Security-first design: Extension loading disabled by default, must be explicitly enabled
    • Supported extensions: FTS5 (full-text search), JSON1, R-Tree (spatial indexing), PCRE (regex), custom user-defined functions
    • Rust NIFs: enable_load_extension/2, load_extension/3 in src/connection.rs
    • Elixir wrappers: EctoLibSql.Native.enable_extensions/2, EctoLibSql.Native.load_ext/3
    • API workflow: Enable extension loading → Load extension(s) → Disable extension loading (recommended)
    • Entry point support: Optional custom entry point function name parameter
    • Platform support: .so (Linux), .dylib (macOS), .dll (Windows)
    • Use cases: Full-text search (FTS5), JSON functions, spatial data (R-Tree), regex matching, custom SQL functions
    • Security warnings: Only load extensions from trusted sources - extensions have full database access
    • Comprehensive documentation with security warnings and common extension examples
  • Statement Parameter Name Introspection (stmt_parameter_name/3)

    • Retrieve parameter names from prepared statements with named parameters
    • Supports all SQLite named parameter styles: :name, @name, $name
    • Use cases: Dynamic query building, parameter validation, better debugging, API introspection
    • Rust NIF: statement_parameter_name() in src/statement.rs
    • Elixir wrapper: EctoLibSql.Native.stmt_parameter_name/3
    • Returns {:ok, "name"} for named parameters (prefix included) or {:ok, nil} for positional ? placeholders
    • Note: Uses 1-based parameter indexing (first parameter is index 1) following SQLite convention
    • Added 5 comprehensive tests covering all three named parameter styles, positional parameters, and mixed parameter scenarios
    • Complements existing stmt_parameter_count/2 for complete parameter introspection
  • Comprehensive Statement Introspection Test Coverage

    • Added 18 edge case tests for prepared statement introspection features (13 existing + 5 parameter_name tests)
    • Parameter introspection edge cases: 0 parameters, 20+ parameters, UPDATE statements, complex nested queries, named parameter introspection
    • Column introspection edge cases: SELECT *, INSERT/UPDATE/DELETE without RETURNING (0 columns), aggregate functions, JOINs, subqueries, computed expressions
    • Improved test coverage for stmt_parameter_count/2, stmt_parameter_name/3, stmt_column_count/2, and stmt_column_name/3
    • All tests verify correct behaviour for simple queries, complex JOINs, aggregates, and edge cases
    • Tests ensure proper handling of aliased columns, expressions, multi-table queries, and all three named parameter styles
    • Location: test/statement_features_test.exs - added 180+ lines of comprehensive edge case tests
  • Statement Reset (reset_stmt/2)

    • Explicitly reset prepared statements to initial state for efficient reuse
    • Performance improvement: 10-15x faster than re-preparing the same SQL string
    • Enables optimal statement reuse pattern: prepare once, execute many times with reset between executions
    • Rust NIF: reset_statement() in src/statement.rs
    • Elixir wrapper: EctoLibSql.Native.reset_stmt/2
    • Added 3 comprehensive tests covering explicit reset, multiple resets, and error handling
    • Usage: EctoLibSql.Native.reset_stmt(state, stmt_id) returns :ok or {:error, reason}
  • Statement Column Metadata (get_stmt_columns/2)

    • Retrieve full column metadata from prepared statements
    • Returns column name, origin name, and declared type for all columns
    • Use cases: Type introspection for dynamic queries, schema discovery, better error messages, type casting hints
    • Rust NIF: get_statement_columns() in src/statement.rs
    • Elixir wrapper: EctoLibSql.Native.get_stmt_columns/2
    • Returns {:ok, [{name, origin_name, decl_type}]} tuples for each column
    • Added 4 comprehensive tests covering basic metadata, aliased columns, expressions, and error handling
    • Supports complex queries with aliases, joins, and aggregate functions
  • Remote Encryption Support for Turso Encrypted Databases

    • Added support for Turso cloud encrypted databases via remote_encryption_key connection option
    • Complements existing local encryption (encryption_key) for at-rest database file encryption
    • Encryption types:
      • Local encryption: AES-256-CBC for local SQLite files (existing feature)
      • Remote encryption: Base64-encoded encryption key sent with each request to Turso (new feature)
    • Connection modes supported: Remote and Remote Replica
    • Usage: remote_encryption_key: "base64-encoded-key" in connection options
    • Remote replica mode can use both local and remote encryption simultaneously for end-to-end encryption
    • Updated Rust NIF: Enhanced connect() in src/connection.rs with EncryptionContext and EncryptionKey::Base64Encoded
    • Updated documentation in README.md with examples for all encryption scenarios
    • See Turso Encryption Documentation for key generation and requirements
  • Comprehensive Elixir Code Quality Tooling

    • Added Credo for static code analysis with strict configuration (.credo.exs)
    • Added Dialyxir for type checking with proper ignore patterns (.dialyzer_ignore.exs)
    • Added Sobelow security scanner for vulnerability detection
    • All three tools integrated into GitHub CI pipeline for automated quality checks
  • Property-Based Fuzz Testing (Elixir)

    • New test/fuzz_test.exs with comprehensive property-based tests using StreamData
    • SQL injection prevention tests - Verifies parameters are safely escaped
    • Transaction behaviour fuzzing - Tests :deferred, :immediate, :exclusive modes
    • Prepared statement fuzzing - Tests various parameter types (integers, strings, floats)
    • Edge case numeric tests - Tests 64-bit integer bounds and special float values
    • Binary/BLOB data handling - Tests arbitrary binary data and round-trip integrity
    • Savepoint name validation - Tests SQL injection prevention in nested transactions
    • Connection ID handling - Tests graceful handling of invalid connection IDs
  • Ecto SQL Adapter Compatibility Tests

    • test/ecto_sql_compatibility_test.exs - Core SQL compatibility tests
    • test/ecto_sql_transaction_compat_test.exs - Transaction compatibility tests
    • test/ecto_stream_compat_test.exs - Streaming compatibility tests
    • Ported key tests from Ecto.Adapters.SQL test suite to verify compatibility
  • Rust Fuzz Testing Infrastructure

    • Added cargo-fuzz based fuzzing in native/ecto_libsql/fuzz/
    • Fuzz targets: fuzz_detect_query_type, fuzz_should_use_query, fuzz_sql_structured
    • CI integration runs each fuzz target for 30 seconds per build
    • Documentation in native/ecto_libsql/FUZZING.md
  • Rust Property-Based Testing

    • Added proptest crate for property-based testing
    • New tests/proptest_tests.rs with structured SQL generation tests
    • Tests query type detection with generated SQL patterns
  • License and Security Advisory Checking

    • Added cargo-deny configuration (deny.toml) for dependency auditing
    • Checks for license compliance, security advisories, duplicate dependencies
    • Integrated into CI pipeline
  • SQLite Hook Investigation (Documented as Unsupported)

    • Researched SQLite update hooks and authorizer hooks for CDC and RLS
    • Added src/hooks.rs with explicit :unsupported returns
    • Documented Rustler threading limitations preventing implementation
    • Added test/hooks_test.exs verifying unsupported behaviour
    • Comprehensive documentation of alternative approaches in CHANGELOG

Changed

  • Elixir Code Quality Improvements

    • Eliminated duplicate code in batch/2 and batch_transactional/2 functions
      • Extracted common parse_batch_results/1 helper function
    • Improved unused variable naming consistency across all test files
      • Changed {:ok, _, _, state} patterns to {:ok, _query, _result, state}
      • Changed {:error, _} patterns to {:error, _reason}
    • Added typespecs to key public functions:
    • Fixed Dialyzer ignore file format - Changed from tuples to regex list
  • Rust Code Modernisation

    • Replaced lazy_static! with std::sync::LazyLock (Rust 1.80+ feature)
    • Added stricter Clippy lints: clippy::unwrap_used, clippy::expect_used
    • Fixed all Clippy warnings across the codebase
    • Improved error handling patterns throughout
  • CI Pipeline Enhancements

    • Added Credo and Sobelow checks to elixir-tests-latest job
    • Added Rust fuzz testing job with 30-second per-target runs
    • Added cargo-deny license and advisory checking
    • Improved caching for faster builds

Fixed

  • Security: SQL Injection Prevention in Pragma Module

    • Added valid_identifier?/1 function to sanitise table names in table_info/2
    • Prevents potential SQL injection via malicious table names
    • Returns {:error, {:invalid_identifier, name}} for invalid identifiers
  • Dialyzer Type Errors

    • Fixed disconnect/2 spec type mismatch with DBConnection behaviour
    • Changed first argument type from Keyword.t() to term()
  • Fuzz Test Stability

    • Fixed savepoint fuzz tests to handle transaction conflicts gracefully
    • Fixed handling of non-UTF8 binary data in NIF calls
    • Added proper try/rescue blocks for ArgumentError in fuzz tests

Clarifications

  • ALTER TABLE ALTER COLUMN Support (Already Implemented)
    • Fully supported since v0.6.0 - libSQL's ALTER COLUMN extension for modifying column attributes
    • Capabilities: Modify type affinity, NOT NULL, CHECK, DEFAULT, and REFERENCES constraints
    • Usage: Use :modify in migrations as with other Ecto adapters
    • Example:
      alter table(:users) do
        modify :age, :string, default: "0"  # Change type and default
        modify :email, :string, null: false # Add NOT NULL constraint
      end
    • Important: Changes only apply to new/updated rows; existing data is not revalidated
    • Implementation: lib/ecto/adapters/libsql/connection.ex:213-219 handles :modify changes
    • SQL output: ALTER TABLE users ALTER COLUMN age TO age TEXT DEFAULT '0'
    • This is a libSQL extension beyond standard SQLite (SQLite does not support ALTER COLUMN)

Investigated but Not Supported

  • Hooks Investigation: Researched implementation of SQLite hooks (update hooks and authorizer hooks) for CDC and row-level security
    • Update Hooks (CDC): Cannot be implemented due to Rustler threading limitations
      • SQLite's update hook runs on managed BEAM threads
      • Rustler's OwnedEnv::send_and_clear() can ONLY be called from unmanaged threads
      • Would cause panic: "send_and_clear: current thread is managed"
    • Authorizer Hooks (RLS): Cannot be implemented due to synchronous callback requirements
      • Requires immediate synchronous response (Allow/Deny/Ignore)
      • No safe way to block waiting for Elixir response from scheduler thread
      • Would risk deadlocks with scheduler thread blocking
    • Result: Both add_update_hook/2, remove_update_hook/1, and add_authorizer/2 return {:error, :unsupported}
    • Alternatives provided: Comprehensive documentation of alternative approaches:
      • For CDC: Application-level events, database triggers, polling, Phoenix.Tracker
      • For RLS: Application-level auth, database views, query rewriting, connection-level privileges
    • See Rustler issue: https://github.com/rusterlium/rustler/issues/293

[0.8.1] - 2025-12-18

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
    • Added comprehensive test coverage for all constraint scenarios (4 new tests)

[0.8.0] - 2025-12-17

Changed

  • Major Rust Code Refactoring (Modularisation)
    • Split monolithic lib.rs (2,302 lines) into 13 focussed, single-responsibility modules
    • Module structure by feature area:
      • connection.rs - Connection lifecycle, establishment, and state management
      • query.rs - Basic query execution and result handling
      • batch.rs - Batch operations (transactional and non-transactional)
      • statement.rs - Prepared statement caching and execution
      • transaction.rs - Transaction management with ownership tracking
      • savepoint.rs - Nested transactions (savepoint operations)
      • cursor.rs - Cursor streaming and result pagination
      • replication.rs - Remote replica sync control and frame tracking
      • metadata.rs - Metadata access (rowid, changes, autocommit status)
      • utils.rs - Shared utilities (safe locking, error handling, row collection)
      • constants.rs - Global registries and configuration constants
      • models.rs - Core data structures (LibSQLConn, connection state)
      • decode.rs - Value decoding and type conversions
    • Test reorganisation - Refactored monolithic tests.rs (1,194 lines) into structured modules:
      • tests/mod.rs - Test module declaration and organisation
      • tests/constants_tests.rs - Registry and constant tests
      • tests/utils_tests.rs - Utility function and safety tests
      • tests/integration_tests.rs - End-to-end integration tests
    • Root module simplification - lib.rs now only declares modules and exports key types
    • Improved maintainability - Separation of concerns
    • Zero behaviour changes - Refactoring is purely organisational, all APIs and functionality preserved
    • Enhanced documentation - Module-level doc comments explain purpose and relationships
    • Impact: Significantly improved code navigation, maintenance, and onboarding for contributors

Fixed

  • Prepared Statement Column Introspection Tests

    • Enabled previously skipped tests for stmt_column_count/2 and stmt_column_name/3 features
    • Tests verify column metadata retrieval from prepared statements works correctly
    • Fixed test references to use correct NIF function names
    • Both simple and complex query scenarios now tested and passing
  • Critical Rust NIF Thread Safety and Scheduler Issues

    • Registry Lock Management: Fixed all functions to drop registry locks before entering TOKIO_RUNTIME.block_on() async blocks
      • execute_batch() and execute_transactional_batch() in batch.rs: Simplified function signatures, dropped conn_map lock before async operations
      • declare_cursor() in cursor.rs: Dropped conn_map lock before async block
      • do_sync() in query.rs: Dropped conn_map lock before async block
      • savepoint(), release_savepoint(), and rollback_to_savepoint() in savepoint.rs: Now use TransactionEntryGuard pattern to avoid holding TXN_REGISTRY lock during async operations
      • prepare_statement() in statement.rs: Now clones inner connection Arc and drops client lock before async block, preventing locks from being held across await points
      • begin_transaction() and begin_transaction_with_behavior() in transaction.rs: Now clone inner connection Arc and drop all locks before async transaction creation, preventing locks from being held across await points
    • DirtyIo Scheduler Annotations: Added #[rustler::nif(schedule = "DirtyIo")] to blocking NIFs
      • last_insert_rowid(), changes(), and is_autocommit() in metadata.rs
      • Prevents blocking the BEAM scheduler during I/O operations
    • Atom Naming Consistency: Renamed remote_primary atom to remote in constants.rs and decode.rs
      • Fixes mismatch between Rust atom (remote_primary()) and Elixir convention (:remote)
      • decode_mode() now correctly decodes :remote atoms from Elixir
    • Binary Allocation Error Handling: Return :error atom instead of nil when binary allocation fails
      • Updated cursor.rs and utils.rs to use :error atom for OwnedBinary::new() allocation failures
      • Provides clearer indication of allocation errors in query results
    • SQL Identifier Quoting: Added proper quoting for SQLite identifiers in PRAGMA queries (utils.rs)
      • Table and index names are now properly quoted with double quotes
      • Internal double quotes are escaped by doubling them
      • Defensive programming against potential edge cases with special characters in identifiers
    • Performance Optimisations:
      • Replication: max_write_replication_index() in replication.rs now calls synchronous method directly instead of wrapping in TOKIO_RUNTIME.block_on()
        • Eliminates unnecessary async overhead for synchronous operations
      • Connection: connect() in connection.rs now uses shared global TOKIO_RUNTIME instead of creating a new runtime per connection
        • Prevents resource exhaustion under high connection rates
        • Eliminates expensive runtime creation overhead (each runtime spawns multiple threads)
        • Aligns with pattern used by all other operations in the codebase
    • Impact: Eliminates potential deadlocks, prevents BEAM scheduler blocking, ensures proper Elixir-Rust atom communication, improves error visibility, reduces overhead for replication index queries
  • Constraint Error Message Handling

    • Enhanced constraint name extraction to support index names in error messages
    • Now correctly extracts custom index names from enhanced error format: (index: index_name)
    • Falls back to column name extraction for standard SQLite error messages
    • Improves unique_constraint/3 matching with custom index names in changesets
    • Clarified documentation on composite unique constraint handling
    • Better support for complex constraint scenarios with multiple columns
  • Remote Turso Tests

    • Reduced test database size by removing unnecessary operations
    • Improved test stability and execution reliability

[0.7.5] - 2025-12-15

Fixed

  • Query/Execute Routing for Batch Operations

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

    • Eliminated per-statement argument clones in batch operations
    • Changed batch_stmts.iter() to batch_stmts.into_iter() to consume vector by value
    • Removed args.clone() calls for non-transactional batch.
    • Removed args.clone() calls for transactional batch.
    • Reduces memory allocations during batch execution for better throughput
  • Lock Coupling Reduction

    • Dropped outer LibSQLConn mutex guard earlier in batch operations
    • Extract inner Arc<Mutex<libsql::Connection>> before entering async block
    • Only hold inner connection lock during I/O operations
    • Applied to execute_batch(), execute_transactional_batch(), execute_batch_native(), and execute_transactional_batch_native()
    • Reduces contention and deadlock surface area
    • Follows established pattern from query_args() function
  • 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)
    • Documented CTE and EXPLAIN detection limitations with clear scope notes
    • Added comprehensive future improvement recommendations with priority levels and implementation sketches
    • Added performance budget note for optimisation efforts

[0.7.0] - 2025-12-09

Added

  • Prepared Statement Caching with Reset

    • Implemented true statement caching: statements are prepared once and reused with .reset() for binding cleanup
    • Changed STMT_REGISTRY from storing SQL text to caching actual Arc<Mutex<Statement>> objects
    • prepare_statement/2 now immediately prepares statements (catches SQL errors early)
    • query_prepared/5 uses cached statement with stmt.reset() call
    • execute_prepared/6 uses cached statement with stmt.reset() call
    • Statement introspection functions optimised to use cached statements directly
    • Eliminates 30-50% performance overhead from repeated statement re-preparation
    • Impact: Significant performance improvement for prepared statement workloads (~10-15x faster for cached queries)
    • Backward compatible: API unchanged, behaviour improved (eager validation better than deferred)
  • Statement Caching Benchmark Test

    • Added test/stmt_caching_benchmark_test.exs with comprehensive caching tests
    • Verified 100 cached executions complete in ~33ms (~330µs per execution)
    • Confirmed bindings clear correctly between executions
    • Tested multiple independent cached statements
    • Demonstrated consistent performance across multiple prepared statements
  • Full Transaction Ownership & Savepoint Connection Context

    • Implemented complete transaction-to-connection mapping with TransactionEntry struct
    • TXN_REGISTRY now tracks conn_id for each transaction, enabling ownership validation
    • Updated begin_transaction/1 and begin_transaction_with_behavior/2 to store connection owner with transaction
    • Updated savepoint/2 NIF signature to savepoint/3 with required conn_id parameter
    • All savepoint functions (savepoint, release_savepoint, rollback_to_savepoint) now validate transaction ownership
    • Updated commit_or_rollback_transaction/5 to validate ownership before commit/rollback
    • Updated declare_cursor_with_context/6 to work with transaction ownership tracking
    • Prevents cross-connection transaction manipulation by enforcing strict ownership validation
    • Returns clear error: "Transaction does not belong to this connection" on ownership violation
    • All 289 tests passing (including 18 savepoint-specific tests, 5 transaction isolation tests)
    • Security: Now validates actual transaction ownership, not just ID existence
  • Connection Management Features

    • busy_timeout/2 - Configure database busy timeout to handle locked databases (default: 5000ms)
    • reset/1 - Reset connection state without closing the connection
    • interrupt/1 - Interrupt long-running queries on a connection
    • All features include comprehensive tests and integration with connection lifecycle
  • PRAGMA Support (Complete SQLite Configuration)

    • New EctoLibSql.Pragma module with comprehensive PRAGMA helpers (396 lines)
    • query/2 - Execute arbitrary PRAGMA statements
    • Foreign Keys: enable_foreign_keys/1, disable_foreign_keys/1, foreign_keys/1
    • Journal Mode: set_journal_mode/2, journal_mode/1 (supports :delete, :wal, :memory, :persist, :truncate, :off)
    • Synchronous Level: set_synchronous/2, synchronous/1 (supports :off, :normal, :full, :extra)
    • Cache Size: set_cache_size/2, cache_size/1 (in pages or KB with negative values)
    • Table Introspection: table_info/2, table_list/1
    • User Version: user_version/1, set_user_version/2 (for schema versioning)
    • Added 19 comprehensive tests covering all PRAGMA operations
  • Native Batch Execution

    • execute_batch_sql/2 - Execute multiple SQL statements in a single call (non-transactional)
    • execute_transactional_batch_sql/2 - Execute multiple SQL statements atomically in a transaction
    • Improved performance for bulk operations (migrations, seeding, etc.)
    • Added 3 comprehensive tests including atomic rollback verification
  • Advanced Replica Sync Control

    • get_frame_number(conn_id) NIF - Monitor replication frame
    • sync_until(conn_id, frame_no) NIF - Wait for specific frame with 30-second timeout
    • flush_replicator(conn_id) NIF - Push pending writes with 30-second timeout
    • Elixir wrappers: get_frame_number_for_replica(), sync_until_frame(), flush_and_get_frame()
    • All with proper error handling, explicit None handling, and network timeouts
    • Improved error messages for timeout and non-replica scenarios
  • Max Write Replication Index

    • max_write_replication_index/1 - Track highest frame number from write operations
    • Enables read-your-writes consistency across replicas
    • Synchronous NIF wrapper around db.max_write_replication_index()
    • Use case: Ensure replica syncs to at least your write frame before reading
  • Prepared Statement Introspection

    • stmt_column_count/2 - Get number of columns in a prepared statement result set
    • stmt_column_name/3 - Get column name by index (0-based)
    • stmt_parameter_count/2 - Get number of parameters (?) in a prepared statement
    • Enables dynamic schema discovery and parameter binding validation
    • Added 21 comprehensive tests in test/prepared_statement_test.exs
  • Savepoint Support (Nested Transactions)

    • create_savepoint/2 - Create a named savepoint within a transaction
    • release_savepoint_by_name/2 - Commit a savepoint's changes
    • rollback_to_savepoint_by_name/2 - Rollback to a savepoint, keeping transaction active
    • Enables nested transaction-like behaviour within a single transaction
    • Perfect for error recovery and partial rollback patterns
    • Added 18 comprehensive tests in test/savepoint_test.exs
  • Test Suite Reorganisation

    • Restructured tests from "missing vs implemented" to feature-based organisation
    • New feature-focused test files:
      • test/connection_features_test.exs (6 tests) - busy_timeout, reset, interrupt
      • test/batch_features_test.exs (6 tests) - batch execution
      • test/pragma_test.exs (19 tests) - PRAGMA operations
      • test/statement_features_test.exs (11 tests) - prepared statement features (mostly skipped, awaiting implementation)
      • test/advanced_features_test.exs (13 tests) - MVCC, cacheflush, replication, extensions, hooks (all skipped, awaiting implementation)
    • All unimplemented features properly marked with @describetag :skip for easy enabling as features are added
  • Comprehensive Documentation Suite

    • TURSO_COMPREHENSIVE_GAP_ANALYSIS.md - Consolidated analysis of all Turso/LibSQL features
    • IMPLEMENTATION_ROADMAP_FOCUSED.md - Detailed implementation roadmap with prioritised phases
    • LIBSQL_FEATURE_MATRIX_FINAL.md - Complete feature compatibility matrix
    • TESTING_PLAN_COMPREHENSIVE.md - Comprehensive testing strategy and coverage plan
    • Merged multiple gap analysis documents into consolidated, authoritative sources
    • Complete source code references and Ecto integration details

Changed

  • LibSQL 0.9.29 API Verification

    • Verified all replication NIFs use correct libsql 0.9.29 APIs
    • get_frame_number/1 confirmed using db.replication_index() (not legacy methods)
    • sync_until/2 confirmed using db.sync_until()
    • flush_replicator/1 confirmed using db.flush_replicator()
    • All implementations verified correct
  • Test Suite Improvements

    • Removed duplicated tests to improve maintainability
    • Standardised test database naming conventions across all test files
    • Improved test assertions for better clarity and debugging
    • Added explicit disconnect calls to match test patterns
    • Enabled previously skipped tests for now-implemented features
    • Fixed test setup issues and race conditions
    • Performance test adjustments for slower CI machines
  • Transaction Ownership Helper Functions

    • Added verify_transaction_ownership/2 helper function to reduce code duplication
    • Simplified ownership validation logic across all transaction operations
    • Consolidated lock scope handling for transaction registry operations
    • Improved code maintainability and consistency
  • Replica Function API Improvements

    • Added state-accepting overloads for replica functions for better ergonomics
    • Fixed inconsistent flush_replicator behaviour to always use 30-second timeout
    • Improved error messages for replica operations

Fixed

  • Security: SQL Injection Prevention

    • Fixed potential SQL injection vulnerability in savepoint name validation
    • Added strict alphanumeric validation for savepoint identifiers
    • Prevents malicious SQL in nested transaction operations
  • Security: Prepared Statement Validation

    • Fixed security issue in prepared statement parameter validation
    • Enhanced parameter binding checks to prevent malformed queries
  • Remote Test Stability

    • Fixed vector operations test to properly drop existing tables before recreation
    • Removed IF NOT EXISTS from table creation to ensure correct schema
    • Prevents "table has no column named X" errors from stale test data
  • Upsert Operations with ON CONFLICT (Issue #25)

    • Implemented full support for on_conflict options in INSERT operations
    • Added support for on_conflict: :nothing with conflict targets (single and composite unique indexes)
    • Added support for on_conflict: :replace_all for upsert operations
    • Added support for custom field replacement with on_conflict: {fields, _, targets}
    • Fixed composite unique index constraint handling - now correctly generates ON CONFLICT (col1, col2) DO NOTHING/UPDATE
    • Improved constraint name extraction from SQLite error messages to handle composite constraints
    • Added 8 comprehensive connection tests for various upsert scenarios
    • Added 154 lines of integration tests demonstrating real-world usage with composite unique indexes
    • Fixed test setup to ensure clean database state by dropping and recreating tables before each test
  • Binary ID Type System (Issue #23) - Complete Resolution

    • Fixed autogenerate(:binary_id) to generate string UUIDs instead of binary UUIDs
    • Fixed loaders(:binary_id) to pass through string UUIDs from TEXT columns (was expecting binary)
    • Fixed dumpers(:binary_id) to pass through string UUIDs (was converting to binary)
    • Fixed Rust NIF to handle Elixir Binary type for BLOB data (was only checking Vec<u8>)
    • LibSQL stores :binary_id as TEXT, not BLOB, so string UUIDs are required throughout the pipeline
    • Prevents "Unsupported argument type" errors when inserting records with binary_id or binary fields
    • Removed unnecessary blob_encode wrapper - binary data now passes through directly
    • Fixed INSERT without RETURNING clause - Now correctly returns rows: nil instead of rows: [] when no RETURNING clause is present, preventing CaseClauseError in Ecto.Adapters.SQL
    • Fixed BLOB encoding in Rust NIF - Binary data now returns as Elixir binaries (<<...>>) instead of lists ([...]), properly encoding BLOBs using OwnedBinary
  • Test Coverage Improvements

    • Added comprehensive integration tests for binary_id autogeneration (6 new tests, all passing)
    • Added end-to-end tests for :binary (BLOB) field CRUD operations
    • Added tests for binary_id with associations and foreign keys
    • Total: 194 lines of new integration tests in test/ecto_integration_test.exs

[0.6.0] - 2025-11-30

Fixed

  • Remote Sync Performance & Reliability

    • Removed redundant manual .sync() calls after write operations for embedded replicas
    • LibSQL automatically handles sync to remote primary database - manual syncs were causing double-sync overhead
    • Added 30-second timeout to connection establishment to prevent indefinite hangs
    • All Turso remote tests now pass reliably (previously 4 tests timed out)
    • Test suite execution time improved significantly (~107s vs timing out at 60s+)
  • Ecto Migrations Compatibility (Issue #20)

    • Fixed DDL function grouping that was causing compilation errors
    • Added comprehensive migration test suite (759 lines) covering all SQLite ALTER TABLE operations
    • Improved handling of SQLite's limited ALTER TABLE support
    • Added tests for column operations, constraint management, and index creation
  • Prepared Statement Execution

    • Fixed panic in prepared statement execution that could crash the BEAM VM
    • Added proper error handling for prepared statement operations
    • Improved error messages for prepared statement failures
  • Extended LibSQL DDL Support

    • Added support for additional ALTER TABLE operations compatible with LibSQL
    • Improved DDL operation grouping and execution order
    • Better handling of SQLite dialect quirks

Added

  • Cursor Streaming Support

    • Implemented cursor-based streaming for large result sets
    • Added handle_declare/4, handle_fetch/4, and handle_deallocate/4 DBConnection callbacks
    • Memory-efficient processing of large queries
    • Rust NIF functions: declare_cursor/3, fetch_cursor/2, cursor registry management
  • Comprehensive Test Coverage

    • Added 138 new DDL generation tests in test/ecto_connection_test.exs
    • Added 759 lines of migration tests in test/ecto_migration_test.exs
    • Improved error handling test coverage
    • All 162 tests passing (0 failures)

Changed

  • Sync Behaviour for Embedded Replicas

    • Automatic sync after writes has been removed (LibSQL handles this natively)
    • Manual sync() via EctoLibSql.Native.sync/1 still available for explicit control
    • Improved sync timeout handling with configurable DEFAULT_SYNC_TIMEOUT_SECS (30s)
    • Added connection timeout to prevent hangs during initial replica sync
  • Documentation Updates

    • Updated all documentation to reflect sync behaviour changes
    • Added clarification about when manual sync is needed vs automatic
    • Improved Turso/LibSQL compatibility documentation references

Technical Details

Sync Performance Before:

  • Manual .sync() called after every write operation
  • Double sync overhead (LibSQL auto-sync + manual sync)
  • 120-second timeout causing long test hangs
  • 4 tests timing out after 60+ seconds each

Sync Performance After:

  • LibSQL's native auto-sync used correctly
  • No redundant manual sync calls
  • 30-second connection timeout for fast failure
  • All tests passing in ~107 seconds

Key Insight: According to Turso documentation: "Writes are sent to the remote primary database by default, then the local database updates automatically once the remote write succeeds." Manual sync is only needed when explicitly pulling down changes from remote (e.g., after reconnecting to an existing replica).

Migration Notes

This is a non-breaking change for normal usage. However, if you were relying on automatic sync behaviour after writes in embedded replica mode, you may now need to explicitly call EctoLibSql.Native.sync/1 when you need to ensure remote data is pulled down (e.g., after reconnecting to an existing local database).

Recommended Actions:

  1. Review code that uses embedded replicas with sync: true
  2. Add explicit sync() calls after reconnecting to existing local databases if you need to pull down remote changes
  3. Remove any redundant manual sync() calls after write operations

[0.5.0] - 2025-11-27

Changed

  • Rust NIF Error Handling (BREAKING for direct NIF users)
    • Eliminated all 146 unwrap() calls from production Rust code
    • Added safe_lock() and safe_lock_arc() helper functions for safe mutex locking
    • All NIF errors now return {:error, message} tuples to Elixir instead of panicking
    • Mutex poisoning errors are handled gracefully with descriptive context
    • Invalid connection/transaction/statement/cursor IDs return proper errors

Fixed

  • VM Stability - NIF errors no longer crash the entire BEAM VM
    • Invalid operations (bad connection IDs, missing resources) now return error tuples
    • Processes survive NIF errors, allowing supervision trees to work properly
    • Error messages include descriptive context for easier debugging

Added

  • Comprehensive Error Handling Tests
    • Added test/error_demo_test.exs with 7 tests demonstrating graceful error handling
    • Added test/error_handling_test.exs with 14 comprehensive error coverage tests
    • All tests verify that NIF errors return proper error tuples instead of crashing the BEAM VM

Technical Details

Before 0.5.0:

  • 146 unwrap() calls in Rust production code
  • Mutex/registry errors → panic → entire BEAM VM crash
  • Invalid IDs → panic → VM crash
  • Supervision trees ineffective for NIF errors

After 0.5.0:

  • 0 unwrap() calls in Rust production code (100% eliminated)
  • All errors return {:error, "descriptive message"} tuples
  • Processes can handle errors and recover
  • Supervision trees work as expected

Migration Guide

This is a non-breaking change for normal Ecto usage. Your existing code will continue to work exactly as before, but is now significantly more stable.

What Changed:

  • NIF functions that previously panicked now return {:error, reason} tuples
  • Your existing error handling code will now catch errors that previously crashed the VM

Recommended Actions:

  1. Review error handling in code that uses EctoLibSql.Native functions directly
  2. Ensure supervision strategies are in place for database operations
  3. Consider adding retry logic for transient errors (connection timeouts, etc.)

Notes

This release represents a major stability improvement for production deployments. The refactoring ensures that ecto_libsql handles errors the "Elixir way" - returning error tuples that can be supervised, rather than panicking at the Rust level and crashing the VM.

[0.4.0] - 2025-11-19

Changed

  • Library Renamed from LibSqlEx to EctoLibSql
    • Package name changed from :libsqlex to :ecto_libsql
    • Main module renamed from LibSqlEx to EctoLibSql
    • Adapter module renamed from Ecto.Adapters.LibSqlEx to Ecto.Adapters.LibSql
    • Connection module renamed from Ecto.Adapters.LibSqlEx.Connection to Ecto.Adapters.LibSql.Connection
    • Native module renamed from LibSqlEx.Native to EctoLibSql.Native
    • All supporting modules updated (Query, Result, State, Error)
    • Rust crate renamed from libsqlex to ecto_libsql

Migration Guide

To upgrade from 0.3.0 to 0.4.0, update your dependencies and module references:

# mix.exs - Update dependency
def deps do
  [
    # Old: {:libsqlex, "~> 0.3.0"}
    {:ecto_libsql, "~> 0.4.0"}
  ]
end

# config/config.exs - Update adapter reference
config :my_app, MyApp.Repo,
  # Old: adapter: Ecto.Adapters.LibSqlEx
  adapter: Ecto.Adapters.LibSql

# Code - Update module references
# Old: alias LibSqlEx.{Query, Result}
alias EctoLibSql.{Query, Result}

# Old: LibSqlEx.Native.vector_type(128, :f32)
EctoLibSql.Native.vector_type(128, :f32)

All functionality remains identical; only names have changed.

[0.3.0] - 2025-11-17

Added

  • Full Ecto Adapter Support - LibSqlEx now provides a complete Ecto adapter implementation

    • Ecto.Adapters.LibSqlEx - Main adapter module implementing Ecto.Adapter.Storage and Ecto.Adapter.Structure
    • Ecto.Adapters.LibSqlEx.Connection - SQL query generation and DDL support for SQLite/libSQL
    • Full support for Ecto schemas, changesets, and migrations
    • Phoenix integration support
    • Type loaders and dumpers for proper Ecto type conversion
    • Storage operations (create, drop, status)
    • Structure operations (dump, load) using sqlite3
    • Migration support with standard Ecto.Migration features:
      • CREATE/DROP TABLE with IF (NOT) EXISTS
      • ALTER TABLE for adding columns and renaming
      • CREATE/DROP INDEX with UNIQUE and partial index support
      • Proper constraint conversion (UNIQUE, FOREIGN KEY, CHECK)
    • Comprehensive test suite for adapter and connection modules
  • Documentation and Examples

    • examples/ecto_example.exs - Complete Ecto usage examples
    • ECTO_MIGRATION_GUIDE.md - Comprehensive migration guide from PostgreSQL/MySQL
    • Updated README with extensive Ecto integration documentation
    • Phoenix integration guide
    • Production deployment best practices

Changed

  • Updated mix.exs to include ecto and ecto_sql dependencies
  • Bumped version from 0.2.0 to 0.3.0 to reflect major feature addition

Notes

This release makes LibSqlEx a full-featured Ecto adapter, bringing it on par with other database adapters in the Elixir ecosystem. Users can now:

  • Use LibSqlEx in Phoenix applications
  • Define Ecto schemas and run migrations
  • Leverage all Ecto.Query features
  • Benefit from Turso's remote replica mode with Ecto
  • Migrate existing applications from PostgreSQL/MySQL to LibSqlEx

The adapter supports all three connection modes:

  1. Local SQLite databases
  2. Remote-only Turso connections
  3. Remote replica mode (local + cloud sync)

Breaking Changes

None - this is purely additive functionality.

Known Limitations

SQLite/libSQL has some limitations compared to PostgreSQL:

  • No ALTER COLUMN support (column type modifications require table recreation)
  • No DROP COLUMN on older SQLite versions (< 3.35.0)
  • No native array types (use JSON or separate tables)
  • No native UUID type (stored as TEXT, works with Ecto.UUID)

These are SQLite limitations, not LibSqlEx limitations, and are well-documented in the migration guide.

[0.2.0] - Previous Release

Added

  • DBConnection protocol implementation
  • Local, Remote, and Remote Replica modes
  • Transaction support with multiple isolation levels
  • Prepared statements
  • Batch operations
  • Cursor support for large result sets
  • Vector search support
  • Encryption support (AES-256-CBC)
  • WebSocket protocol support
  • Metadata methods (last_insert_rowid, changes, etc.)

[0.1.0] - Initial Release

Added

  • Basic LibSQL/Turso connection support
  • Rust NIF implementation
  • Query execution
  • Basic transaction support