Skip to content

Implement InsertQuery.Ignore() and improve targetless DoNothing()#157

Closed
cungminh2710 wants to merge 1 commit into
mainfrom
feat/insert-ignore-9814084095486085146
Closed

Implement InsertQuery.Ignore() and improve targetless DoNothing()#157
cungminh2710 wants to merge 1 commit into
mainfrom
feat/insert-ignore-9814084095486085146

Conversation

@cungminh2710

Copy link
Copy Markdown
Contributor

This PR adds the .Ignore() method to InsertQuery, bringing Rain ORM closer to feature parity with Drizzle ORM. It also improves the implementation of targetless ON CONFLICT DO NOTHING across all supported dialects.

Key changes:

  • Added Ignore() method to InsertQuery.
  • Integrated Ignore() with MySQL's INSERT IGNORE and SQLite's INSERT OR IGNORE.
  • PostgreSQL correctly errors on .Ignore(), guiding users toward the idiomatic .OnConflict().DoNothing().
  • Refactored writeConflictClause to allow targetless DoNothing() for PostgreSQL and SQLite.
  • Updated MySQL's OnConflict().DoNothing() to utilize INSERT IGNORE logic, providing better performance and cleaner SQL than the previous no-op assignment hack.
  • Removed the now-obsolete mysqlConflictNoopColumn helper.
  • Added unit tests covering all new and refactored behaviors across PostgreSQL, SQLite, and MySQL.

PR created automatically by Jules for task 9814084095486085146 started by @cungminh2710

…ing()

Implemented .Ignore() for InsertQuery to support dialect-native ignore semantics:
- MySQL: INSERT IGNORE
- SQLite: INSERT OR IGNORE
- PostgreSQL: Returns error (use .OnConflict().DoNothing() instead)

Improved targetless .OnConflict().DoNothing() support:
- PostgreSQL: ON CONFLICT DO NOTHING
- SQLite: Renders as INSERT OR IGNORE
- MySQL: Renders as INSERT IGNORE (replaces brittle no-op update)

Cleaned up technical debt by removing mysqlConflictNoopColumn helper.
Added comprehensive tests for all dialects.

Co-authored-by: cungminh2710 <8063319+cungminh2710@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an Ignore() method to InsertQuery (rendering INSERT IGNORE on MySQL and INSERT OR IGNORE on SQLite, with a compile-time error on PostgreSQL), and refactors writeConflictClause to natively support targetless ON CONFLICT DO NOTHING for PostgreSQL and targetless DoNothing() via INSERT OR IGNORE for SQLite.

  • writeInsertInto is introduced as a shared helper that resolves which INSERT keyword variant to emit based on dialect, useIgnore, and the active conflict action.
  • MySQL's OnConflict().DoNothing() now emits INSERT IGNORE INTO instead of the previous ON DUPLICATE KEY UPDATE id = id no-op, removing the mysqlConflictNoopColumn helper entirely.
  • Tests are added for all new paths across PostgreSQL, SQLite, and MySQL, including the compile-time PostgreSQL error and the updated MySQL DoNothing SQL shape.

Confidence Score: 3/5

The Ignore() addition and PostgreSQL/SQLite targetless DoNothing() paths are clean and well-tested, but the MySQL OnConflict().DoNothing() change quietly alters which errors are surfaced for existing callers.

The switch from ON DUPLICATE KEY UPDATE id = id to INSERT IGNORE for MySQL DoNothing() is a behavioural change that will silently swallow non-duplicate-key errors (truncation, FK violations, generated-column failures) that previously propagated to callers. No test or migration note covers this difference, so any MySQL application relying on strict-mode error propagation alongside OnConflict().DoNothing() would be silently broken after this change.

pkg/rain/query_insert.go — specifically the MySQL writeConflictClause DoNothing branch and the writeInsertInto MySQL path.

Important Files Changed

Filename Overview
pkg/rain/query_insert.go Adds useIgnore field, Ignore() method, and refactors writeInsertInto/writeConflictClause. The MySQL OnConflict().DoNothing() path now emits INSERT IGNORE instead of the no-op ON DUPLICATE KEY UPDATE idiom, silently broadening error suppression beyond duplicate-key violations.
pkg/rain/query_insert_test.go Adds unit tests for Ignore(), targetless DoNothing() for PostgreSQL and SQLite, and updated MySQL DoNothing() SQL expectation. Coverage is adequate for the happy paths but lacks tests for non-duplicate-key error propagation on MySQL.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[InsertQuery.compile] --> B{selectQuery?}
    B -- yes --> C[writeSelectSQL]
    B -- no --> D[writeValuesSQL]
    C --> E[writeInsertInto]
    D --> E

    E --> F{renderIgnore?}
    F -- useIgnore=true OR\nMySQL+DoNothing OR\nSQLite+targetless+DoNothing --> G{dialect}
    G -- mysql --> H[INSERT IGNORE INTO]
    G -- sqlite --> I[INSERT OR IGNORE INTO]
    G -- postgres & useIgnore --> J[error: use OnConflict DoNothing]
    G -- default & useIgnore --> K[error: not implemented]
    F -- false --> L[INSERT INTO]

    H & I & L --> M[writeTableName]
    M --> N[writeConflictClause]

    N --> O{dialect}
    O -- mysql+DoNothing --> P[return nil\nhandled by INSERT IGNORE]
    O -- mysql+DoUpdateSet --> Q[ON DUPLICATE KEY UPDATE ...]
    O -- postgres+targetless+DoNothing --> R[ON CONFLICT DO NOTHING]
    O -- sqlite+targetless+DoNothing --> S[return nil\nhandled by INSERT OR IGNORE]
    O -- postgres/sqlite+targeted --> T[ON CONFLICT col DO NOTHING/UPDATE]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[InsertQuery.compile] --> B{selectQuery?}
    B -- yes --> C[writeSelectSQL]
    B -- no --> D[writeValuesSQL]
    C --> E[writeInsertInto]
    D --> E

    E --> F{renderIgnore?}
    F -- useIgnore=true OR\nMySQL+DoNothing OR\nSQLite+targetless+DoNothing --> G{dialect}
    G -- mysql --> H[INSERT IGNORE INTO]
    G -- sqlite --> I[INSERT OR IGNORE INTO]
    G -- postgres & useIgnore --> J[error: use OnConflict DoNothing]
    G -- default & useIgnore --> K[error: not implemented]
    F -- false --> L[INSERT INTO]

    H & I & L --> M[writeTableName]
    M --> N[writeConflictClause]

    N --> O{dialect}
    O -- mysql+DoNothing --> P[return nil\nhandled by INSERT IGNORE]
    O -- mysql+DoUpdateSet --> Q[ON DUPLICATE KEY UPDATE ...]
    O -- postgres+targetless+DoNothing --> R[ON CONFLICT DO NOTHING]
    O -- sqlite+targetless+DoNothing --> S[return nil\nhandled by INSERT OR IGNORE]
    O -- postgres/sqlite+targeted --> T[ON CONFLICT col DO NOTHING/UPDATE]
Loading

Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
pkg/rain/query_insert.go:836-839
**MySQL `INSERT IGNORE` silences more errors than the old no-op idiom**

The old `ON DUPLICATE KEY UPDATE id = id` approach surfaces every error _except_ a duplicate-key violation. `INSERT IGNORE` converts **all** MySQL errors — data truncation, out-of-range values, FK violations, generated-column failures, etc. — into warnings and silently discards the affected row. Any existing caller of `OnConflict().DoNothing()` on MySQL that relied on non-duplicate-key errors being propagated will now silently swallow those errors without any indication that something went wrong. The PR description does not call out this behavioral difference, and there are no tests that verify non-duplicate-key errors still surface.

### Issue 2 of 2
pkg/rain/query_insert.go:745-776
**`Ignore()` + `OnConflict().DoUpdateSet()` combination is not validated on MySQL**

When `useIgnore` is `true` and the conflict action is `insertConflictActionDoUpdateSet`, `writeInsertInto` writes `INSERT IGNORE INTO`, then `writeConflictClause` appends `ON DUPLICATE KEY UPDATE ...`. The resulting SQL — `INSERT IGNORE INTO … ON DUPLICATE KEY UPDATE col = ?` — is syntactically valid in MySQL but semantically contradictory: the `ON DUPLICATE KEY UPDATE` clause specifically handles duplicate-key conflicts, while `INSERT IGNORE` silently discards all other errors. A user who calls `.Ignore().OnConflict().DoUpdateSet(col)` likely expects an error, not silently contradictory SQL. Consider adding a validation that `useIgnore` and `DoUpdateSet` are mutually exclusive for MySQL.

Reviews (1): Last reviewed commit: "feat(rain): add Ignore() to InsertQuery ..." | Re-trigger Greptile

Comment thread pkg/rain/query_insert.go
Comment on lines 836 to 839
if q.conflict.action == insertConflictActionDoNothing {
noopColumn, err := mysqlConflictNoopColumn(q.table)
if err != nil {
return err
}
ctx.writeString(" ON DUPLICATE KEY UPDATE ")
ctx.writeQuotedIdentifier(noopColumn.Name)
ctx.writeString(" = ")
ctx.writeQuotedIdentifier(noopColumn.Name)
// Handled by INSERT IGNORE in writeInsertInto
return nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 MySQL INSERT IGNORE silences more errors than the old no-op idiom

The old ON DUPLICATE KEY UPDATE id = id approach surfaces every error except a duplicate-key violation. INSERT IGNORE converts all MySQL errors — data truncation, out-of-range values, FK violations, generated-column failures, etc. — into warnings and silently discards the affected row. Any existing caller of OnConflict().DoNothing() on MySQL that relied on non-duplicate-key errors being propagated will now silently swallow those errors without any indication that something went wrong. The PR description does not call out this behavioral difference, and there are no tests that verify non-duplicate-key errors still surface.

Prompt To Fix With AI
This is a comment left during a code review.
Path: pkg/rain/query_insert.go
Line: 836-839

Comment:
**MySQL `INSERT IGNORE` silences more errors than the old no-op idiom**

The old `ON DUPLICATE KEY UPDATE id = id` approach surfaces every error _except_ a duplicate-key violation. `INSERT IGNORE` converts **all** MySQL errors — data truncation, out-of-range values, FK violations, generated-column failures, etc. — into warnings and silently discards the affected row. Any existing caller of `OnConflict().DoNothing()` on MySQL that relied on non-duplicate-key errors being propagated will now silently swallow those errors without any indication that something went wrong. The PR description does not call out this behavioral difference, and there are no tests that verify non-duplicate-key errors still surface.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

Comment thread pkg/rain/query_insert.go
Comment on lines +745 to +776
func (q *InsertQuery) writeInsertInto(ctx *compileContext) error {
dialectName := ctx.dialect.Name()

renderIgnore := q.useIgnore
if q.conflict != nil && q.conflict.action == insertConflictActionDoNothing {
if dialectName == "mysql" {
renderIgnore = true
} else if dialectName == "sqlite" && len(q.conflict.columns) == 0 && q.conflict.constraint == "" {
renderIgnore = true
}
}

if renderIgnore {
switch dialectName {
case "mysql":
ctx.writeString("INSERT IGNORE INTO ")
case "sqlite":
ctx.writeString("INSERT OR IGNORE INTO ")
case "postgres":
if q.useIgnore {
return errors.New("rain: .Ignore() is not supported for PostgreSQL; use .OnConflict().DoNothing() instead")
}
ctx.writeString("INSERT INTO ")
default:
if q.useIgnore {
return fmt.Errorf("rain: .Ignore() is not implemented for %s dialect", dialectName)
}
ctx.writeString("INSERT INTO ")
}
} else {
ctx.writeString("INSERT INTO ")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Ignore() + OnConflict().DoUpdateSet() combination is not validated on MySQL

When useIgnore is true and the conflict action is insertConflictActionDoUpdateSet, writeInsertInto writes INSERT IGNORE INTO, then writeConflictClause appends ON DUPLICATE KEY UPDATE .... The resulting SQL — INSERT IGNORE INTO … ON DUPLICATE KEY UPDATE col = ? — is syntactically valid in MySQL but semantically contradictory: the ON DUPLICATE KEY UPDATE clause specifically handles duplicate-key conflicts, while INSERT IGNORE silently discards all other errors. A user who calls .Ignore().OnConflict().DoUpdateSet(col) likely expects an error, not silently contradictory SQL. Consider adding a validation that useIgnore and DoUpdateSet are mutually exclusive for MySQL.

Prompt To Fix With AI
This is a comment left during a code review.
Path: pkg/rain/query_insert.go
Line: 745-776

Comment:
**`Ignore()` + `OnConflict().DoUpdateSet()` combination is not validated on MySQL**

When `useIgnore` is `true` and the conflict action is `insertConflictActionDoUpdateSet`, `writeInsertInto` writes `INSERT IGNORE INTO`, then `writeConflictClause` appends `ON DUPLICATE KEY UPDATE ...`. The resulting SQL — `INSERT IGNORE INTO … ON DUPLICATE KEY UPDATE col = ?` — is syntactically valid in MySQL but semantically contradictory: the `ON DUPLICATE KEY UPDATE` clause specifically handles duplicate-key conflicts, while `INSERT IGNORE` silently discards all other errors. A user who calls `.Ignore().OnConflict().DoUpdateSet(col)` likely expects an error, not silently contradictory SQL. Consider adding a validation that `useIgnore` and `DoUpdateSet` are mutually exclusive for MySQL.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

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.

1 participant