Skip to content

Conversation

@ocean
Copy link
Owner

@ocean ocean commented Dec 31, 2025

Summary

  • Extends the on_conflict implementation to support Ecto.Query-based updates
  • Allows keyword list syntax for dynamic update operations during upsert
  • Basic UPSERT (:nothing, :replace_all, {:replace, fields}) was already implemented

Changes

  • Add on_conflict pattern matching for %Ecto.Query{} in connection.ex
  • Add update_all_for_on_conflict/1 helper function for SQL generation
  • Add 3 new tests for query-based on_conflict (set operation, inc operation, error case)
  • Document UPSERT operations in AGENTS.md with examples
  • Update CHANGELOG.md with new feature

Example Usage

# Query-based update with keyword list syntax
{:ok, user} = Repo.insert(changeset,
  on_conflict: [set: [name: "Updated Name", updated_at: DateTime.utc_now()]],
  conflict_target: [:email]
)

# Increment counter on conflict
{:ok, counter} = Repo.insert(counter_changeset,
  on_conflict: [inc: [count: 1]],
  conflict_target: [:key]
)

Test Plan

  • All existing on_conflict tests pass (7 tests)
  • New query-based on_conflict tests pass (3 tests)
  • Full test suite passes (470 tests)
  • Formatting verified

Closes beads issue: el-ndz

Summary by CodeRabbit

Release Notes

  • New Features

    • Extended UPSERT functionality to support dynamic query-based conflict handling, including field set and increment operations for more flexible data management.
  • Documentation

    • Added comprehensive UPSERT documentation with practical examples covering various conflict resolution strategies, composite unique indexes, and LibSQL-specific configuration requirements.

✏️ Tip: You can customize this high-level summary in your review settings.

Extends the on_conflict implementation to support Ecto.Query-based
updates, allowing keyword list syntax like:
  on_conflict: [set: [name: "value"], inc: [count: 1]]

Changes:
- Add on_conflict pattern for %Ecto.Query{} in connection.ex
- Add update_all_for_on_conflict/1 helper for SQL generation
- Add 3 new tests for query-based on_conflict
- Document UPSERT operations in AGENTS.md and CHANGELOG.md

Closes el-ndz
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 31, 2025

Walkthrough

This pull request adds support for query-based UPSERT operations in the LibSQL adapter, enabling INSERT ... ON CONFLICT ... DO UPDATE with dynamic field updates via Ecto queries. The changes include documentation, implementation of a query-to-update-set converter, and test coverage for set, increment, and error scenarios.

Changes

Cohort / File(s) Summary
Documentation
AGENTS.md, CHANGELOG.md
Added UPSERT documentation with on_conflict option examples and query-based UPSERT changelog entry describing support for :set and :inc operations with required conflict_target
Implementation
lib/ecto/adapters/libsql/connection.ex
Introduced update_all_for_on_conflict/1 helper to generate UPDATE SET clauses from queries; integrated query-based on_conflict clause generation with validation for empty targets
Test Coverage
test/ecto_connection_test.exs
Added three new tests: query-based on_conflict with set operation, increment operation, and ArgumentError when conflict_target is missing

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A query hops into conflict resolution,
With sets and increments in smooth evolution,
On SQLite's unique constraint we land,
UPSERT flows now flourish across the land!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the primary change: adding query-based on_conflict support for UPSERT operations. The title is specific, clear, and directly reflects the main feature introduced across all modified files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature-upsert-support

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b7da8b and 873de1c.

📒 Files selected for processing (4)
  • AGENTS.md
  • CHANGELOG.md
  • lib/ecto/adapters/libsql/connection.ex
  • test/ecto_connection_test.exs
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ex,exs,rs,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Use British/Australian English for all code, comments, and documentation (except SQL keywords and compatibility requirements)

Files:

  • CHANGELOG.md
  • AGENTS.md
  • lib/ecto/adapters/libsql/connection.ex
  • test/ecto_connection_test.exs
{AGENTS.md,CHANGELOG.md,README.md,ECTO_MIGRATION_GUIDE.md,RUST_ERROR_HANDLING.md,TESTING.md}

📄 CodeRabbit inference engine (CLAUDE.md)

Update documentation in AGENTS.md, CHANGELOG.md, and README.md when adding new features or making significant changes

Files:

  • CHANGELOG.md
  • AGENTS.md
AGENTS.md

📄 CodeRabbit inference engine (AGENT.md)

Document agent architecture and design patterns in AGENTS.md

Files:

  • AGENTS.md
**/*.ex

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ex: Use prepared statements with automatic statement caching for repeated queries to achieve ~10-15x performance improvement over unprepared queries
Use batch operations (EctoLibSql.Native.batch/2 or EctoLibSql.Native.batch_transactional/2) for bulk inserts instead of individual insert statements to reduce roundtrips
Always handle EctoLibSql function results with pattern matching on {:ok, ...} and {:error, ...} tuples to prevent crashes, never use bare unwrap patterns
Use transactions (EctoLibSql.handle_begin/2, handle_commit/2, handle_rollback/2) with :immediate or :exclusive behavior for write-heavy workloads to prevent database lock conflicts
Use DBConnection.stream/3 with cursors for memory-efficient processing of large result sets instead of loading all rows into memory
Use vector search (EctoLibSql.Native.vector_type/2, vector/1, vector_distance_cos/2) for AI/ML applications requiring semantic similarity or RAG (Retrieval-Augmented Generation) functionality
Enable PRAGMA foreign_keys constraint, set journal_mode to WAL (Write-Ahead Logging) for better concurrency, and configure appropriate cache_size for production deployments
Use savepoints (EctoLibSql.Native.create_savepoint/2, release_savepoint_by_name/2, rollback_to_savepoint_by_name/2) for partial rollback and error recovery within transactions
Implement circuit breaker patterns or retry logic with exponential backoff for remote Turso connections to handle transient failures gracefully
For remote replica mode, use EctoLibSql.Native.sync_until_frame/2 or flush_and_get_frame/1 to ensure read-your-writes consistency for critical operations
Configure busy_timeout/2 to handle database locking scenarios gracefully, with longer timeouts for write-heavy concurrent workloads
Never use freeze_replica/1 function as it is not implemented; instead use backup/restore or application-level failover patterns for disaster recovery
Use EctoLibSql.Native.enable_extensions/2 and load_ext/3 only when necessary and only ...

Files:

  • lib/ecto/adapters/libsql/connection.ex
**/*.{ex,exs,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

ALWAYS format code before committing: run mix format --check-formatted and cargo fmt

Files:

  • lib/ecto/adapters/libsql/connection.ex
  • test/ecto_connection_test.exs
**/*.{ex,exs}

📄 CodeRabbit inference engine (CLAUDE.md)

Use proper error handling patterns in Elixir: case match or with clauses to handle {:ok, ...} and {:error, ...} tuple returns

Files:

  • lib/ecto/adapters/libsql/connection.ex
  • test/ecto_connection_test.exs
{migrations/**/*.exs,lib/ecto/adapters/libsql/connection.ex}

📄 CodeRabbit inference engine (CLAUDE.md)

For SQLite ALTER COLUMN or DROP COLUMN operations, use table recreation pattern: create new table with desired schema, copy data, drop old table, rename new table, and recreate indexes

Files:

  • lib/ecto/adapters/libsql/connection.ex
test/**/*.exs

📄 CodeRabbit inference engine (AGENTS.md)

Use logging, inspection of connection state (state.mode, state.conn_id), and metadata functions (get_last_insert_rowid/1, get_changes/1, get_total_changes/1) for debugging database operations

Add comprehensive tests covering happy path, error cases, edge cases, transaction rollback, type conversions, and concurrent operations

Files:

  • test/ecto_connection_test.exs
🧠 Learnings (15)
📚 Learning: 2025-12-31T04:34:12.798Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T04:34:12.798Z
Learning: Applies to {AGENTS.md,CHANGELOG.md,README.md,ECTO_MIGRATION_GUIDE.md,RUST_ERROR_HANDLING.md,TESTING.md} : Update documentation in AGENTS.md, CHANGELOG.md, and README.md when adding new features or making significant changes

Applied to files:

  • CHANGELOG.md
  • AGENTS.md
📚 Learning: 2025-12-30T11:31:53.999Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T11:31:53.999Z
Learning: Applies to priv/repo/migrations/*.exs : Use ALTER COLUMN modifications in migrations for type changes, NOT NULL constraints, DEFAULT values, and REFERENCES (foreign keys) when targeting libSQL deployments

Applied to files:

  • CHANGELOG.md
  • AGENTS.md
  • lib/ecto/adapters/libsql/connection.ex
  • test/ecto_connection_test.exs
📚 Learning: 2025-12-30T11:31:53.999Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T11:31:53.999Z
Learning: Applies to **/*.ex : Use savepoints (EctoLibSql.Native.create_savepoint/2, release_savepoint_by_name/2, rollback_to_savepoint_by_name/2) for partial rollback and error recovery within transactions

Applied to files:

  • CHANGELOG.md
  • AGENTS.md
📚 Learning: 2025-12-30T11:31:53.999Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T11:31:53.999Z
Learning: Applies to **/{schemas,models}/*.ex : Use Ecto schemas with Ecto.Schema, Ecto.Changeset for data validation, and Ecto.Query for composable database queries in Phoenix applications

Applied to files:

  • CHANGELOG.md
📚 Learning: 2025-12-30T11:31:53.999Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T11:31:53.999Z
Learning: Applies to **/*.ex : Use transactions (EctoLibSql.handle_begin/2, handle_commit/2, handle_rollback/2) with :immediate or :exclusive behavior for write-heavy workloads to prevent database lock conflicts

Applied to files:

  • CHANGELOG.md
  • AGENTS.md
  • lib/ecto/adapters/libsql/connection.ex
  • test/ecto_connection_test.exs
📚 Learning: 2025-12-31T04:34:12.798Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T04:34:12.798Z
Learning: Applies to {migrations/**/*.exs,lib/ecto/adapters/libsql/connection.ex} : For SQLite ALTER COLUMN or DROP COLUMN operations, use table recreation pattern: create new table with desired schema, copy data, drop old table, rename new table, and recreate indexes

Applied to files:

  • AGENTS.md
  • lib/ecto/adapters/libsql/connection.ex
  • test/ecto_connection_test.exs
📚 Learning: 2025-12-30T11:31:53.999Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T11:31:53.999Z
Learning: Applies to **/*.ex : Use batch operations (EctoLibSql.Native.batch/2 or EctoLibSql.Native.batch_transactional/2) for bulk inserts instead of individual insert statements to reduce roundtrips

Applied to files:

  • AGENTS.md
📚 Learning: 2025-12-31T04:34:12.798Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T04:34:12.798Z
Learning: Applies to native/ecto_libsql/src/**/*.rs : Drop locks before async operations to avoid holding locks across await points

Applied to files:

  • AGENTS.md
📚 Learning: 2025-12-30T11:31:53.999Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T11:31:53.999Z
Learning: Applies to test/**/*.exs : Use logging, inspection of connection state (state.mode, state.conn_id), and metadata functions (get_last_insert_rowid/1, get_changes/1, get_total_changes/1) for debugging database operations

Applied to files:

  • AGENTS.md
  • test/ecto_connection_test.exs
📚 Learning: 2025-12-30T11:31:53.999Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T11:31:53.999Z
Learning: Applies to **/*.ex : For remote replica mode, use EctoLibSql.Native.sync_until_frame/2 or flush_and_get_frame/1 to ensure read-your-writes consistency for critical operations

Applied to files:

  • AGENTS.md
📚 Learning: 2025-12-31T04:34:12.798Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T04:34:12.798Z
Learning: Applies to native/ecto_libsql/src/**/*.rs : Choose appropriate Rust modules based on functionality: connection lifecycle → `connection.rs`, query execution → `query.rs`, transactions → `transaction.rs`, batch operations → `batch.rs`, statements → `statement.rs`, cursors → `cursor.rs`, replication → `replication.rs`, metadata → `metadata.rs`, savepoints → `savepoint.rs`, utilities → `utils.rs`

Applied to files:

  • AGENTS.md
  • lib/ecto/adapters/libsql/connection.ex
📚 Learning: 2025-12-31T04:34:12.798Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T04:34:12.798Z
Learning: Applies to native/ecto_libsql/src/{connection,transaction,query}.rs : Use immediate transactions (`begin(state, behavior: :immediate)`) for write-heavy workloads to avoid 'database is locked' errors

Applied to files:

  • AGENTS.md
📚 Learning: 2025-12-30T11:31:53.999Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-30T11:31:53.999Z
Learning: Applies to **/*.ex : Always handle EctoLibSql function results with pattern matching on {:ok, ...} and {:error, ...} tuples to prevent crashes, never use bare unwrap patterns

Applied to files:

  • lib/ecto/adapters/libsql/connection.ex
📚 Learning: 2025-12-31T04:34:12.798Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T04:34:12.798Z
Learning: Applies to test/**/*.exs : Add comprehensive tests covering happy path, error cases, edge cases, transaction rollback, type conversions, and concurrent operations

Applied to files:

  • test/ecto_connection_test.exs
📚 Learning: 2025-12-31T04:34:12.798Z
Learnt from: CR
Repo: ocean/ecto_libsql PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-31T04:34:12.798Z
Learning: Applies to native/ecto_libsql/src/tests/**/*.rs : Tests MAY use `.unwrap()` for simplicity

Applied to files:

  • test/ecto_connection_test.exs
🧬 Code graph analysis (1)
test/ecto_connection_test.exs (1)
lib/ecto/adapters/libsql/connection.ex (1)
  • insert (528-549)
⏰ 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 / ubuntu-latest
  • GitHub Check: Elixir 1.18.0 / OTP 27.0 / macos-latest
  • GitHub Check: Analyze (rust)
🔇 Additional comments (7)
lib/ecto/adapters/libsql/connection.ex (2)

594-601: Query-based on_conflict implementation looks correct.

The pattern matching correctly distinguishes between missing and present conflict targets, raising an appropriate error when required. The SQL generation delegates to update_all_for_on_conflict/1 for the UPDATE SET clause, which maintains consistency with the existing update_all/1 implementation.


621-626: Helper function correctly reuses existing update logic.

The update_all_for_on_conflict/1 function appropriately leverages the existing update_fields/2 helper, ensuring consistency with the regular update_all/1 implementation. This approach correctly handles both :set and :inc operations defined in update_op/5 (lines 1098-1104).

AGENTS.md (1)

225-271: Comprehensive UPSERT documentation with clear examples.

The documentation thoroughly covers all on_conflict options including the new query-based syntax. The examples are practical and demonstrate both :set and :inc operations. The notes correctly highlight the LibSQL/SQLite requirement for explicit :conflict_target, distinguishing it from PostgreSQL behaviour.

CHANGELOG.md (1)

12-26: Clear changelog entry documenting the new feature.

The changelog entry comprehensively describes the query-based UPSERT support with practical examples, implementation references, and test coverage notes. The examples align with the documentation in AGENTS.md and correctly demonstrate the keyword list syntax for :set and :inc operations.

test/ecto_connection_test.exs (3)

713-743: Comprehensive test for query-based on_conflict with set operation.

The test correctly simulates Ecto's internal query structure with proper from, sources, and updates fields. The assertions verify that the generated SQL includes all required clauses: INSERT, VALUES, ON CONFLICT with target, and UPDATE SET with the literal value.


745-771: Good test coverage for inc operation in query-based on_conflict.

The test verifies that the :inc operation correctly generates the SQL syntax "count" = "count" + 1 for incrementing fields on conflict. This ensures the update_op(:inc, ...) function (line 1102-1104 in connection.ex) is properly integrated with the query-based on_conflict path.


773-797: Proper error handling test for missing conflict_target.

The test correctly verifies that attempting query-based on_conflict without a conflict_target raises an ArgumentError with the specific message defined in the implementation. This ensures the LibSQL/SQLite requirement for explicit conflict targets is enforced.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ocean ocean merged commit a5bd085 into main Dec 31, 2025
14 checks passed
@ocean ocean deleted the feature-upsert-support branch December 31, 2025 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants