Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions pkg/rain/compile_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,31 @@ func BenchmarkInsertToSQL(b *testing.B) {
}
}
})

b.Run("SingleModel", func(b *testing.B) {
type User struct {
schema.TableModel
ID int64 `db:"id"`
Email string `db:"email"`
Name string `db:"name"`
Active bool `db:"active"`
}
u := &User{
Email: "bolt@example.com",
Name: "Bolt",
Active: true,
}

b.ReportAllocs()
b.ResetTimer()
for range b.N {
_, _, err := db.Insert().
Table(users).
Model(u).
ToSQL()
if err != nil {
b.Fatal(err)
}
}
})
}
75 changes: 75 additions & 0 deletions pkg/rain/query_insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,13 @@ func (q *InsertQuery) assignmentsFromModelAndSet() ([]assignment, error) {
}

func (q *InsertQuery) writeSingleRowSQL(ctx *compileContext) error {
// OPTIMIZATION: If only a model is provided (no .Set() calls), stream
// directly from the model using its assignment plan to avoid intermediate
// slice and map allocations in assignmentsFromModelAndSet.
if q.model != nil && len(q.values) == 0 {
return q.writeSingleModelSQL(ctx)
}

// Single row might be from a model and/or explicit .Set() values.
// We use assignmentsFromModelAndSet which is already
// relatively efficient for a single row.
Expand Down Expand Up @@ -554,6 +561,74 @@ func (q *InsertQuery) writeSingleRowSQL(ctx *compileContext) error {
return nil
}

func (q *InsertQuery) writeSingleModelSQL(ctx *compileContext) error {
meta, value, err := lookupModelMeta(q.model)
if err != nil {
return err
}
plan, err := lookupModelAssignmentPlan(q.table, value.Type())
if err != nil {
return err
}

// First pass: determine which fields to include and write column names.
type activeField struct {
column *schema.ColumnDef
value any
}
var activeFieldsBuf [16]activeField
activeFields := activeFieldsBuf[:0]

for _, f := range plan.fields {
fieldValue := value.FieldByIndex(f.index)
resolved, include := fieldValueForInsert(f.column, fieldValue, true)
if include {
field := activeField{column: f.column, value: resolved}
if len(activeFields) < cap(activeFieldsBuf) {
activeFields = append(activeFields, field)
} else {
if len(activeFields) == cap(activeFieldsBuf) {
// Fallback to heap if model has >16 active columns.
newFields := make([]activeField, len(activeFields), len(plan.fields))
copy(newFields, activeFields)
activeFields = newFields
}
activeFields = append(activeFields, field)
}
}
}

if len(activeFields) == 0 {
return errors.New("rain: insert model produced no values")
}

ctx.writeString(" (")
for idx, f := range activeFields {
if idx > 0 {
ctx.writeString(", ")
}
ctx.writeQuotedIdentifier(f.column.Name)
}
ctx.writeString(") VALUES (")

ctx.ensureArgsCapacity(len(activeFields))

for idx, f := range activeFields {
if idx > 0 {
ctx.writeString(", ")
}
if err := ctx.writeAny(f.value); err != nil {
return err
}
}
ctx.writeByte(')')

// Validate strict binding if requested (already checked by lookupModelAssignmentPlan
// but we re-check metadata here to be safe and consistent with assignmentsFromModel).
_ = meta
return nil
}

func (q *InsertQuery) writeSelectSQL(ctx *compileContext) error {
if err := writeCTEs(ctx, q.ctes, "insert"); err != nil {
return err
Expand Down
42 changes: 42 additions & 0 deletions pkg/rain/query_model_assignments.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,31 @@ func fieldValueForInsert(column *schema.ColumnDef, fieldValue reflect.Value, ski
return nil, false
}

// OPTIMIZATION: Fast-path for common built-in primitive types to check for
// zero values before calling the more expensive insertValueForField.
// We only use the fast-path for types without a PkgPath (built-in) to
// ensure custom providers (wrappers) are still consulted for auto IDs.
if skipAuto && column.AutoIncrement && fieldValue.Type().PkgPath() == "" {
switch fieldValue.Kind() {
case reflect.Int64, reflect.Int, reflect.Int32, reflect.Int16, reflect.Int8:
if val := fieldValue.Int(); val == 0 {
return nil, false
}
case reflect.Uint64, reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8:
if val := fieldValue.Uint(); val == 0 {
return nil, false
}
case reflect.String:
if val := fieldValue.String(); val == "" {
return nil, false
}
case reflect.Pointer:
if fieldValue.IsNil() {
return nil, false
}
}
}

resolvedValue, include, explicit := insertValueForField(fieldValue)
if !include {
return nil, false
Expand Down Expand Up @@ -139,6 +164,23 @@ func insertValueForField(fieldValue reflect.Value) (value any, include bool, exp
return nil, false, false
}

// OPTIMIZATION: Fast-path for common built-in primitive types to avoid
// interface conversions and extra reflection overhead where possible.
// We only use the fast-path for types without a PkgPath (built-in) to
// ensure custom providers (wrappers) are still consulted.
if fieldValue.Type().PkgPath() == "" {
switch kind {
case reflect.Int64:
return fieldValue.Int(), true, false
case reflect.String:
return fieldValue.String(), true, false
case reflect.Bool:
return fieldValue.Bool(), true, false
case reflect.Int:
return fieldValue.Int(), true, false
}
}

if fieldValue.CanInterface() {
if setter, ok := fieldValue.Interface().(setValueProvider); ok {
value, include = setter.rainSetValue()
Expand Down