feature: Add random rowid support (libSQL extension)#39
Conversation
WalkthroughAdds LibSQL extension support for RANDOM ROWID and ALTER COLUMN, refactors CREATE TABLE DDL to return {table_constraints, table_suffix}, enforces mutual-exclusivity rules for RANDOM ROWID, and adds tests and documentation for these features. Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (4)
🧰 Additional context used📓 Path-based instructions (7)AGENTS.md📄 CodeRabbit inference engine (AGENT.md)
Files:
**/*.{ex,exs,rs,md}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{ex,exs}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
test/**/*.exs📄 CodeRabbit inference engine (CLAUDE.md)
Files:
lib/**/*.ex📄 CodeRabbit inference engine (CLAUDE.md)
Files:
lib/ecto/adapters/libsql/connection.ex📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.ex📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (24)📓 Common learnings📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:26.477ZApplied to files:
📚 Learning: 2025-12-17T11:58:46.019ZApplied to files:
🪛 markdownlint-cli2 (0.18.1)CHANGELOG.md132-132: Bare URL used (MD034, no-bare-urls) ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
🔇 Additional comments (14)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
CHANGELOG.md (1)
10-65: Duplicate### Addedsections under the same version.There are two
### Addedheadings under the## [Unreleased]section (lines 10 and 65). Consider merging them into a single### Addedsection for consistency with the Keep a Changelog format.lib/ecto/adapters/libsql/connection.ex (1)
401-407: Consider adding validation for mutually exclusive options.Per the libSQL specification documented in the PR, RANDOM ROWID is mutually exclusive with WITHOUT ROWID and AUTOINCREMENT. Currently, the implementation doesn't validate these constraints—libSQL will reject invalid combinations at runtime, but earlier validation could provide clearer error messages.
🔎 Example validation (optional)
# In table_options/2, before constructing the suffix: if table.options && Keyword.get(table.options, :random_rowid, false) do if Keyword.get(table.options, :without_rowid, false) do raise ArgumentError, "RANDOM ROWID cannot be combined with WITHOUT ROWID" end # Check for AUTOINCREMENT in columns has_autoincrement = Enum.any?(columns, fn {:add, _name, _type, opts} -> Keyword.get(opts, :autoincrement, false) end) if has_autoincrement do raise ArgumentError, "RANDOM ROWID cannot be combined with AUTOINCREMENT" end " RANDOM ROWID" else "" end
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
AGENTS.mdCHANGELOG.mdlib/ecto/adapters/libsql/connection.extest/ecto_connection_test.exs
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ex,exs,rs,md}
📄 CodeRabbit inference engine (CLAUDE.md)
ALWAYS use British/Australian English spelling and grammar for code, comments, and documentation, except where required for function calls, SQL keywords, error messages, or compatibility with external systems that may use US English
Files:
CHANGELOG.mdAGENTS.mdlib/ecto/adapters/libsql/connection.extest/ecto_connection_test.exs
AGENTS.md
📄 CodeRabbit inference engine (AGENT.md)
Document agent architecture and design patterns in AGENTS.md
Update documentation in AGENTS.md API Reference section and CHANGELOG.md when adding or modifying public APIs
Files:
AGENTS.md
**/*.{ex,exs}
📄 CodeRabbit inference engine (CLAUDE.md)
ALWAYS run the Elixir formatter (
mix format --check-formatted) before committing changes and fix any issues
Files:
lib/ecto/adapters/libsql/connection.extest/ecto_connection_test.exs
lib/**/*.ex
📄 CodeRabbit inference engine (CLAUDE.md)
lib/**/*.ex: When updating Elixir code, follow the pattern of case matching on NIF results:{:ok, _, result, new_state}for successful query execution or{:error, reason}for failures
In Elixir error handling, preferwithclauses for chaining multiple operations that return Result tuples, providing clear error handling with anelseclause
In Ecto changesets, use thecastfunction to automatically attempt type conversions for fields, which will raise ChangeError if types don't match schema expectations
Use transactions with appropriate isolation behaviours when executing database operations::deferredfor mixed workloads,:immediatefor write-heavy operations,:exclusivefor critical operations requiring exclusive locks,:read_onlyfor read-only queries
When working with database locks, use transactions with proper timeout configuration, ensure connections are properly closed in try-after blocks, and use immediate/exclusive transaction behaviours for write-heavy workloads to minimise lock contention
When handling vector search operations, verify LibSQL version supports vectors, use correct vector syntax withEctoLibSql.Native.vector()function, insert vectors withvector(?)SQL syntax, and query with vector distance functions likevector_distance_cos
Files:
lib/ecto/adapters/libsql/connection.ex
lib/ecto/adapters/libsql/connection.ex
📄 CodeRabbit inference engine (CLAUDE.md)
In lib/ecto/adapters/libsql/connection.ex, implement SQL expression handlers for SQLite functions by pattern matching on function names and converting them to SQL fragments, respecting SQLite syntax and limitations
Files:
lib/ecto/adapters/libsql/connection.ex
**/*.ex
📄 CodeRabbit inference engine (AGENTS.md)
**/*.ex: Use prepared statements viaEctoLibSql.Native.prepare/2andEctoLibSql.Native.query_stmt/3for repeated queries to improve performance by ~10-15x
Use batch operations withEctoLibSql.Native.batch_transactional/2for bulk inserts/updates instead of individual statements
Implement proper error handling by pattern matching on{:ok, ...}and{:error, ...}tuples from all EctoLibSql operations
Use transactions withEctoLibSql.handle_begin/2,EctoLibSql.handle_commit/2, andEctoLibSql.handle_rollback/2for multi-step database operations
Use cursor streaming withDBConnection.stream/3for memory-efficient processing of large result sets
Use vector search withEctoLibSql.Native.vector/1,EctoLibSql.Native.vector_type/2, andEctoLibSql.Native.vector_distance_cos/2for AI/ML features
UseEctoLibSql.Pragma.*functions to configure SQLite parameters such as foreign keys, journal mode, cache size, and synchronous level
Use savepoints withEctoLibSql.Native.create_savepoint/2,EctoLibSql.Native.release_savepoint_by_name/2, andEctoLibSql.Native.rollback_to_savepoint_by_name/2for nested transactions
Use Ecto changesets for data validation before inserting or updating records
Use Ecto query composition withEcto.Queryfor building complex queries incrementally
Preload associations withRepo.preload/2to avoid N+1 query problems in Ecto applications
For remote replicas in production, useEctoLibSql.Native.sync/1,EctoLibSql.Native.get_frame_number_for_replica/1,EctoLibSql.Native.sync_until_frame/2, andEctoLibSql.Native.flush_and_get_frame/1for replication management
Use busy timeout configuration withEctoLibSql.Native.busy_timeout/2to handle database locking conflicts gracefully
Use statement introspection functions (EctoLibSql.Native.stmt_parameter_count/2,EctoLibSql.Native.stmt_column_count/2,EctoLibSql.Native.stmt_column_name/3) to validate prepared statement structure
Handle connection errors gracefully by ch...
Files:
lib/ecto/adapters/libsql/connection.ex
test/**/*.exs
📄 CodeRabbit inference engine (CLAUDE.md)
test/**/*.exs: Add tests for all new features in appropriate Elixir test files (test/ecto_*_test.exs) and Rust test modules (native/ecto_libsql/src/tests/), covering happy path, error cases, edge cases, and type conversions
Add comprehensive tests for unsupported functions asserting that they always return {:error, :unsupported} and that they don't modify database state or corrupt connections
Files:
test/ecto_connection_test.exs
🧠 Learnings (23)
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to lib/ecto_libsql/native.ex : For explicitly unsupported functions, update the Elixir wrapper with comprehensive documentation explaining why the feature is unsupported, what architectural constraints prevent it, and what alternative approaches users can take
Applied to files:
CHANGELOG.mdAGENTS.mdlib/ecto/adapters/libsql/connection.ex
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to test/**/*migration*test.exs : For SQLite migrations that require column type changes or drops (which SQLite doesn't support via ALTER), use the table recreation pattern: create new table, copy data with transformations, swap tables, and recreate indexes
Applied to files:
CHANGELOG.mdAGENTS.mdlib/ecto/adapters/libsql/connection.extest/ecto_connection_test.exs
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to lib/ecto/adapters/libsql/connection.ex : In lib/ecto/adapters/libsql/connection.ex, implement SQL expression handlers for SQLite functions by pattern matching on function names and converting them to SQL fragments, respecting SQLite syntax and limitations
Applied to files:
CHANGELOG.mdAGENTS.mdlib/ecto/adapters/libsql/connection.ex
📚 Learning: 2025-12-17T11:58:46.019Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T11:58:46.019Z
Learning: Applies to **/*.ex : Use `EctoLibSql.Pragma.*` functions to configure SQLite parameters such as foreign keys, journal mode, cache size, and synchronous level
Applied to files:
CHANGELOG.mdAGENTS.mdlib/ecto/adapters/libsql/connection.ex
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to lib/ecto/adapters/libsql.ex : When implementing Ecto storage operations (create, drop, status) in lib/ecto/adapters/libsql.ex, ensure they properly handle both local SQLite and remote Turso databases via the connection mode in EctoLibSql.State
Applied to files:
CHANGELOG.mdAGENTS.mdlib/ecto/adapters/libsql/connection.ex
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to lib/**/*.ex : When handling vector search operations, verify LibSQL version supports vectors, use correct vector syntax with `EctoLibSql.Native.vector()` function, insert vectors with `vector(?)` SQL syntax, and query with vector distance functions like `vector_distance_cos`
Applied to files:
CHANGELOG.mdAGENTS.md
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to AGENTS.md : Update documentation in AGENTS.md API Reference section and CHANGELOG.md when adding or modifying public APIs
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to native/ecto_libsql/src/!(tests)/**/*.rs : Include comprehensive Rust documentation comments (/// comments) on all public NIF functions explaining purpose, arguments, return values (success and error cases), and usage examples
Applied to files:
AGENTS.mdtest/ecto_connection_test.exs
📚 Learning: 2025-12-17T11:58:46.019Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T11:58:46.019Z
Learning: Applies to priv/repo/migrations/*.exs : Use Ecto migrations with `Ecto.Migration` for all database schema changes
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to lib/ecto/adapters/libsql.ex : In type loaders/dumpers in lib/ecto/adapters/libsql.ex, ensure bidirectional conversion: loaders convert SQLite values to Elixir types on retrieval, dumpers convert Elixir types to SQLite on storage
Applied to files:
AGENTS.mdlib/ecto/adapters/libsql/connection.ex
📚 Learning: 2025-12-17T11:58:46.019Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T11:58:46.019Z
Learning: Applies to **/*.ex : Use savepoints with `EctoLibSql.Native.create_savepoint/2`, `EctoLibSql.Native.release_savepoint_by_name/2`, and `EctoLibSql.Native.rollback_to_savepoint_by_name/2` for nested transactions
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:46.019Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T11:58:46.019Z
Learning: Applies to **/*.ex : Use transactions with `EctoLibSql.handle_begin/2`, `EctoLibSql.handle_commit/2`, and `EctoLibSql.handle_rollback/2` for multi-step database operations
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:46.019Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T11:58:46.019Z
Learning: Applies to **/*.ex : Use batch operations with `EctoLibSql.Native.batch_transactional/2` for bulk inserts/updates instead of individual statements
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to lib/**/*.ex : Use transactions with appropriate isolation behaviours when executing database operations: `:deferred` for mixed workloads, `:immediate` for write-heavy operations, `:exclusive` for critical operations requiring exclusive locks, `:read_only` for read-only queries
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:46.019Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T11:58:46.019Z
Learning: Applies to **/*.ex : Use prepared statements via `EctoLibSql.Native.prepare/2` and `EctoLibSql.Native.query_stmt/3` for repeated queries to improve performance by ~10-15x
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:46.019Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T11:58:46.019Z
Learning: Applies to **/*.ex : For remote replicas in production, use `EctoLibSql.Native.sync/1`, `EctoLibSql.Native.get_frame_number_for_replica/1`, `EctoLibSql.Native.sync_until_frame/2`, and `EctoLibSql.Native.flush_and_get_frame/1` for replication management
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:46.019Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T11:58:46.019Z
Learning: Applies to **/repo.ex : Define custom Ecto repositories using `Ecto.Repo` with `Ecto.Adapters.LibSql` adapter for ecto_libsql projects
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:46.019Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T11:58:46.019Z
Learning: Applies to **/*_schema.{ex,exs} : Use Ecto schemas with proper type definitions for database models in Elixir applications
Applied to files:
AGENTS.md
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to test/**/*.exs : Add tests for all new features in appropriate Elixir test files (test/ecto_*_test.exs) and Rust test modules (native/ecto_libsql/src/tests/), covering happy path, error cases, edge cases, and type conversions
Applied to files:
test/ecto_connection_test.exs
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to test/**/*.exs : Add comprehensive tests for unsupported functions asserting that they always return {:error, :unsupported} and that they don't modify database state or corrupt connections
Applied to files:
test/ecto_connection_test.exs
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to native/ecto_libsql/src/!(tests)/**/*.rs : When adding a new NIF function in Rust, place it in the appropriate module based on functionality: connection.rs (lifecycle), query.rs (execution), transaction.rs (transactions), batch.rs (batch operations), statement.rs (prepared statements), cursor.rs (streaming), replication.rs (replica sync), metadata.rs (metadata access), or savepoint.rs (nested transactions)
Applied to files:
test/ecto_connection_test.exs
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to lib/ecto_libsql/native.ex : For each new Rust NIF function, add corresponding Elixir NIF stubs and safe wrapper functions in lib/ecto_libsql/native.ex, with proper state management via EctoLibSql.State
Applied to files:
test/ecto_connection_test.exs
📚 Learning: 2025-12-17T11:58:26.477Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-17T11:58:26.477Z
Learning: Applies to native/ecto_libsql/src/!(tests)/**/*.rs : In Rust async blocks within NIFs, drop registry locks before entering the `TOKIO_RUNTIME.block_on(async { ... })` call to prevent deadlocks, then re-acquire locks if needed after the async block completes
Applied to files:
test/ecto_connection_test.exs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Elixir 1.18.0 / OTP 27.0 / macos-latest
- GitHub Check: Elixir 1.18.0 / OTP 27.0 / ubuntu-latest
- GitHub Check: Analyze (rust)
🔇 Additional comments (5)
AGENTS.md (2)
1301-1357: LGTM! Comprehensive documentation for RANDOM ROWID feature.The documentation clearly covers:
- Usage patterns with code examples
- Security benefits (preventing ID enumeration attacks)
- Compatibility with composite primary keys and IF NOT EXISTS
- Restrictions (mutual exclusivity with WITHOUT ROWID and AUTOINCREMENT)
- SQL output examples
1359-1401: LGTM! Clear documentation for ALTER COLUMN support.The documentation appropriately:
- Lists supported modification types
- Warns that changes only apply to new/updated rows
- Notes this is a libSQL extension not available in standard SQLite
test/ecto_connection_test.exs (1)
49-120: LGTM! Comprehensive test coverage for RANDOM ROWID feature.The tests thoroughly cover:
- Single primary key with RANDOM ROWID
- Composite primary key with RANDOM ROWID (verifying both constraints are present)
- Negative case when
random_rowid: false- Default behaviour when no options specified
- Combination with IF NOT EXISTS
The regex assertions correctly verify RANDOM ROWID placement after the closing parenthesis.
lib/ecto/adapters/libsql/connection.ex (2)
186-191: LGTM! Clean refactoring of DDL generation.The two-part tuple approach
{table_constraints, table_suffix}elegantly separates:
- Internal table constraints (composite primary keys) placed inside the parentheses
- External table suffixes (RANDOM ROWID) placed after the closing parenthesis
The string interpolation correctly positions both components in the final SQL.
386-410: Well-structured implementation with clear separation of concerns.The function correctly:
- Separates composite PK constraints (inside parentheses) from table suffixes (after parentheses)
- Handles nil options safely with
table.options && Keyword.get(...)- Returns a descriptive tuple for the caller to use
This aligns well with the libSQL specification where RANDOM ROWID must appear after the table definition.
options: [random_rowid: true]tocreate table()in migrationsCREATE TABLE sessions (...) RANDOM ROWIDSummary by CodeRabbit
New Features
Documentation
Tests
Bug Fixes/Validation
✏️ Tip: You can customize this high-level summary in your review settings.