diff --git a/pkg/rain/compile_bench_test.go b/pkg/rain/compile_bench_test.go index 79c037b..371f326 100644 --- a/pkg/rain/compile_bench_test.go +++ b/pkg/rain/compile_bench_test.go @@ -76,6 +76,22 @@ func BenchmarkSelectToSQL(b *testing.B) { ToSQL() } }) + + b.Run("InClause1000", func(b *testing.B) { + vals := make([]int64, 1000) + for i := range 1000 { + vals[i] = int64(i) + } + + b.ReportAllocs() + b.ResetTimer() + for range b.N { + _, _, _ = db.Select(). + Table(users). + Where(users.ID.In(vals...)). + ToSQL() + } + }) } func BenchmarkInsertToSQL(b *testing.B) { diff --git a/pkg/rain/query_compile.go b/pkg/rain/query_compile.go index 83e5117..d4224d1 100644 --- a/pkg/rain/query_compile.go +++ b/pkg/rain/query_compile.go @@ -308,6 +308,8 @@ func (c *compileContext) writeJoinedPredicates(predicates []schema.Predicate, wr return c.writePredicate(predicates[0]) } + c.ensureArgsCapacity(len(predicates)) + if wrap { c.writeByte('(') } @@ -398,6 +400,7 @@ func (c *compileContext) writeExpressionInContext(expr schema.Expression, contex default: return fmt.Errorf("rain: invalid binary operator %q", value.Operator) } + c.ensureArgsCapacity(2) c.writeByte('(') if err := c.writeExpression(value.Left); err != nil { return err @@ -413,6 +416,7 @@ func (c *compileContext) writeExpressionInContext(expr schema.Expression, contex if len(value.Exprs) < 2 { return errors.New("rain: CONCAT requires at least two expressions") } + c.ensureArgsCapacity(len(value.Exprs)) switch c.dialect.Name() { case "postgres", "sqlite": c.writeByte('(') @@ -443,6 +447,7 @@ func (c *compileContext) writeExpressionInContext(expr schema.Expression, contex if len(value.Values) == 0 { return errors.New("rain: IN predicate requires at least one value") } + c.ensureArgsCapacity(len(value.Values) + 1) if err := c.writeExpression(value.Left); err != nil { return err } @@ -451,20 +456,23 @@ func (c *compileContext) writeExpressionInContext(expr schema.Expression, contex } else { c.writeString(" IN (") } + + // OPTIMIZATION: Hoist expressionContext outside the loop and reuse it. + ctx := expressionContext{} + if len(value.Values) == 1 { + ctx.noParens = true + } for idx, item := range value.Values { if idx > 0 { c.writeString(", ") } - ctx := expressionContext{} - if len(value.Values) == 1 { - ctx.noParens = true - } if err := c.writeExpressionInContext(item, ctx); err != nil { return err } } c.writeByte(')') case schema.BetweenExpr: + c.ensureArgsCapacity(3) if err := c.writeExpression(value.Left); err != nil { return err } @@ -515,6 +523,7 @@ func (c *compileContext) writeExpressionInContext(expr schema.Expression, contex c.writeString(" IS NULL") } case schema.LogicalExpr: + c.ensureArgsCapacity(len(value.Exprs)) c.writeByte('(') for idx, part := range value.Exprs { if idx > 0 { @@ -531,6 +540,16 @@ func (c *compileContext) writeExpressionInContext(expr schema.Expression, contex if len(value.WhenThenPairs) == 0 { return errors.New("rain: CASE expression requires at least one WHEN clause") } + // OPTIMIZATION: Hint capacity for WHEN/THEN pairs plus optional value and else. + n := len(value.WhenThenPairs) * 2 + if value.ValueExpression != nil { + n++ + } + if value.ElseExpression != nil { + n++ + } + c.ensureArgsCapacity(n) + c.writeByte('(') c.writeString("CASE") if value.ValueExpression != nil { @@ -584,6 +603,7 @@ func (c *compileContext) writeExpressionInContext(expr schema.Expression, contex if len(value.Exprs) < 2 { return errors.New("rain: COALESCE requires at least two expressions") } + c.ensureArgsCapacity(len(value.Exprs)) c.writeString("COALESCE(") for idx, part := range value.Exprs { if part == nil { @@ -631,6 +651,9 @@ func (c *compileContext) writeColumnName(column schema.ColumnReference) { } func (c *compileContext) writeRaw(raw schema.RawExpr) error { + if len(raw.Args) > 0 { + c.ensureArgsCapacity(len(raw.Args)) + } argIndex := 0 for idx := range len(raw.SQL) { if raw.SQL[idx] != '?' {