-
Notifications
You must be signed in to change notification settings - Fork 0
Add INSERT IGNORE and INSERT OR IGNORE support #150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ type InsertQuery struct { | |
| conflict *insertConflictClause | ||
| ctes []cteDefinition | ||
| defaultValues bool | ||
| ignore bool | ||
|
|
||
| // OPTIMIZATION: Internal buffers to avoid heap allocations for common | ||
| // query shapes while keeping the struct size reasonable. | ||
|
|
@@ -216,6 +217,13 @@ func (q *InsertQuery) Returning(exprs ...schema.Expression) *InsertQuery { | |
| return q | ||
| } | ||
|
|
||
| // Ignore configures the INSERT to ignore conflicts. | ||
| // MySQL renders "INSERT IGNORE", SQLite renders "INSERT OR IGNORE". | ||
| func (q *InsertQuery) Ignore() *InsertQuery { | ||
| q.ignore = true | ||
| return q | ||
| } | ||
|
|
||
| // Prepare compiles and prepares the INSERT query. | ||
| func (q *InsertQuery) Prepare(ctx context.Context) (*PreparedInsertQuery, error) { | ||
| if q.runner == nil { | ||
|
|
@@ -287,7 +295,7 @@ func (q *InsertQuery) writeValuesSQL(ctx *compileContext) error { | |
| if err := q.validateSources(); err != nil { | ||
| return err | ||
| } | ||
| ctx.writeString("INSERT INTO ") | ||
| q.writeInsertInto(ctx) | ||
| ctx.writeTableName(q.table) | ||
| if ctx.dialect.Name() == "mysql" { | ||
| ctx.writeString(" () VALUES ()") | ||
|
|
@@ -299,7 +307,7 @@ func (q *InsertQuery) writeValuesSQL(ctx *compileContext) error { | |
| return err | ||
| } | ||
|
|
||
| ctx.writeString("INSERT INTO ") | ||
| q.writeInsertInto(ctx) | ||
| ctx.writeTableName(q.table) | ||
|
|
||
| if q.models != nil { | ||
|
|
@@ -521,6 +529,36 @@ func (q *InsertQuery) assignmentsFromModelAndSet() ([]assignment, error) { | |
| return assignments, nil | ||
| } | ||
|
|
||
| func (q *InsertQuery) writeInsertInto(ctx *compileContext) { | ||
| ctx.writeString("INSERT") | ||
| if q.useIgnore(ctx) { | ||
| switch ctx.dialect.Name() { | ||
| case "mysql": | ||
| ctx.writeString(" IGNORE") | ||
| case "sqlite": | ||
| ctx.writeString(" OR IGNORE") | ||
| } | ||
| } | ||
| ctx.writeString(" INTO ") | ||
| } | ||
|
|
||
| func (q *InsertQuery) useIgnore(ctx *compileContext) bool { | ||
| if q.ignore { | ||
| return true | ||
| } | ||
| if q.conflict != nil && q.conflict.action == insertConflictActionDoNothing { | ||
| // Drizzle behavior: .onConflictDoNothing() without target columns on | ||
| // MySQL or SQLite renders as IGNORE. | ||
| if ctx.dialect.Name() == "mysql" { | ||
| return true | ||
|
Comment on lines
+552
to
+553
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Existing MySQL callers of Context Used: AGENTS.md (source) Prompt To Fix With AIThis is a comment left during a code review.
Path: pkg/rain/query_insert.go
Line: 552-553
Comment:
**MySQL Ignore Broadens Errors**
Existing MySQL callers of `.OnConflict().DoNothing()` now get `INSERT IGNORE` instead of the old no-op `ON DUPLICATE KEY UPDATE` clause. That ignores more than duplicate keys: invalid values, truncation, or NOT NULL problems can be downgraded into warnings and coerced/default data, where the old query would still return an error.
**Context Used:** AGENTS.md ([source](https://app.greptile.com/hyperlocalise/github/hyperlocalise/rain-orm/-/custom-context?memory=3b24bd19-4fb7-4328-8fc3-52795c121ec6))
How can I resolve this? If you propose a fix, please make it concise. |
||
| } | ||
| if ctx.dialect.Name() == "sqlite" && len(q.conflict.columns) == 0 && q.conflict.constraint == "" { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| func (q *InsertQuery) writeSingleRowSQL(ctx *compileContext) error { | ||
| // Single row might be from a model and/or explicit .Set() values. | ||
| // We use assignmentsFromModelAndSet which is already | ||
|
|
@@ -574,7 +612,7 @@ func (q *InsertQuery) writeSelectSQL(ctx *compileContext) error { | |
| selectQuery = selectQuery.withSQLiteInsertSelectConflictWhere() | ||
| } | ||
|
|
||
| ctx.writeString("INSERT INTO ") | ||
| q.writeInsertInto(ctx) | ||
| ctx.writeTableName(q.table) | ||
|
|
||
| if len(q.columns) > 0 { | ||
|
|
@@ -710,6 +748,9 @@ func (q *InsertQuery) writeConflictClause(ctx *compileContext) error { | |
| return errors.New("rain: MySQL ON DUPLICATE KEY UPDATE does not support WHERE filters") | ||
| } | ||
| if q.conflict.action == insertConflictActionDoNothing { | ||
| if q.useIgnore(ctx) { | ||
| return nil | ||
| } | ||
| noopColumn, err := mysqlConflictNoopColumn(q.table) | ||
| if err != nil { | ||
| return err | ||
|
|
@@ -743,6 +784,9 @@ func (q *InsertQuery) writeConflictClause(ctx *compileContext) error { | |
| } | ||
|
|
||
| if len(q.conflict.columns) == 0 && q.conflict.constraint == "" { | ||
| if q.useIgnore(ctx) { | ||
| return nil | ||
| } | ||
|
Comment on lines
786
to
+789
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For SQLite, Context Used: AGENTS.md (source) Prompt To Fix With AIThis is a comment left during a code review.
Path: pkg/rain/query_insert.go
Line: 786-789
Comment:
**Target Predicate Gets Dropped**
For SQLite, `.OnConflict().Where(predicate).DoNothing()` has no columns or constraint but does have a target predicate. This shortcut treats it as untargeted ignore and emits `INSERT OR IGNORE`, silently discarding the predicate and ignoring all constraint failures instead of reporting the incomplete conflict target.
**Context Used:** AGENTS.md ([source](https://app.greptile.com/hyperlocalise/github/hyperlocalise/rain-orm/-/custom-context?memory=3b24bd19-4fb7-4328-8fc3-52795c121ec6))
How can I resolve this? If you propose a fix, please make it concise. |
||
| return errors.New("rain: conflict clause requires at least one target (columns or constraint)") | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a Postgres caller uses
.Ignore()without also adding.OnConflict(), this branch marks ignore as active butwriteInsertInto()has no Postgres rendering case andwriteConflictClause()returns because no conflict clause exists. The query compiles as a plainINSERT, so duplicate-key conflicts still fail instead of being ignored or rejected as unsupported.Context Used: AGENTS.md (source)
Prompt To Fix With AI