github.com/octohelm/storage@v0.0.0-20240516030302-1ac2cc1ea347/pkg/sqlbuilder/expr_test.go (about)

     1  package sqlbuilder_test
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"database/sql/driver"
     7  	"fmt"
     8  	"testing"
     9  
    10  	"github.com/octohelm/storage/internal/testutil"
    11  	. "github.com/octohelm/storage/pkg/sqlbuilder"
    12  	testingx "github.com/octohelm/x/testing"
    13  )
    14  
    15  func TestResolveExpr(t *testing.T) {
    16  	t.Run("empty", func(t *testing.T) {
    17  		testingx.Expect(t, ResolveExpr(nil), testingx.Be[*Ex](nil))
    18  	})
    19  }
    20  
    21  type Byte uint8
    22  
    23  func TestEx(t *testing.T) {
    24  	t.Run("empty query", func(t *testing.T) {
    25  		testutil.ShouldBeExpr(t, Expr(""), "")
    26  	})
    27  
    28  	t.Run("named arg", func(t *testing.T) {
    29  		testutil.ShouldBeExpr(t,
    30  			Expr(`time > @left AND time < @right`, sql.Named("left", 1), sql.Named("right", 10)),
    31  			"time > ? AND time < ?", 1, 10,
    32  		)
    33  
    34  		testutil.ShouldBeExpr(t,
    35  			Expr(`time > @left AND time < @right`, NamedArgSet{
    36  				"left":  1,
    37  				"right": 10,
    38  			}),
    39  			"time > ? AND time < ?", 1, 10,
    40  		)
    41  
    42  		t.Run("nested named arg", func(t *testing.T) {
    43  			testutil.ShouldBeExpr(t,
    44  				Expr(`CREATE TABLE IF NOT EXISTS @table (@col)`, NamedArgSet{
    45  					"table": Expr("t"),
    46  					"col": Expr("@col @type", NamedArgSet{
    47  						"col":  Expr("f_id"),
    48  						"type": Expr("int"),
    49  					}),
    50  				}),
    51  				"CREATE TABLE IF NOT EXISTS t (f_id int)",
    52  			)
    53  		})
    54  	})
    55  
    56  	t.Run("flatten slice", func(t *testing.T) {
    57  		testutil.ShouldBeExpr(t,
    58  			Expr(`#ID IN (?)`, []int{28, 29, 30}),
    59  			"#ID IN (?,?,?)", 28, 29, 30,
    60  		)
    61  	})
    62  
    63  	t.Run("flatten slice for slice with named byte", func(t *testing.T) {
    64  		fID := TypedCol[int]("f_id")
    65  		fValue := TypedCol[Byte]("f_value")
    66  
    67  		testutil.ShouldBeExpr(t,
    68  			And(
    69  				And(nil, fID.V(In(28))),
    70  				fValue.V(In(Byte(28))),
    71  			),
    72  			"((f_id IN (?))) AND (f_value IN (?))", 28, Byte(28),
    73  		)
    74  	})
    75  
    76  	t.Run("flatten should skip for bytes", func(t *testing.T) {
    77  		testutil.ShouldBeExpr(t,
    78  			Expr(`#ID = (?)`, []byte("")),
    79  			"#ID = (?)", []byte(""),
    80  		)
    81  	})
    82  
    83  	t.Run("flatten with sub expr ", func(t *testing.T) {
    84  		testutil.ShouldBeExpr(t,
    85  			Expr(`#ID = ?`, Expr("#ID + ?", 1)),
    86  			"#ID = #ID + ?", 1,
    87  		)
    88  	})
    89  
    90  	t.Run("flatten with ValuerExpr", func(t *testing.T) {
    91  		testutil.ShouldBeExpr(t,
    92  			Expr(`#Point = ?`, Point{1, 1}),
    93  			"#Point = ST_GeomFromText(?)", Point{1, 1},
    94  		)
    95  	})
    96  }
    97  
    98  func BenchmarkEx(b *testing.B) {
    99  	b.Run("empty query", func(b *testing.B) {
   100  		for i := 0; i < b.N; i++ {
   101  			_ = Expr("").Ex(context.Background())
   102  		}
   103  	})
   104  
   105  	b.Run("flatten slice", func(b *testing.B) {
   106  		for i := 0; i < b.N; i++ {
   107  			Expr(`#ID IN (?)`, []int{28, 29, 30}).Ex(context.Background())
   108  		}
   109  	})
   110  
   111  	b.Run("flatten with sub expr", func(b *testing.B) {
   112  		b.Run("raw", func(b *testing.B) {
   113  			eb := Expr("")
   114  			eb.Grow(2)
   115  
   116  			eb.WriteQuery("#ID > ?")
   117  			eb.WriteQuery(" AND ")
   118  			eb.WriteQuery("#ID < ?")
   119  
   120  			eb.AppendArgs(1, 10)
   121  
   122  			rawBuild := func() *Ex {
   123  				return eb.Ex(context.Background())
   124  			}
   125  
   126  			clone := func(ex *Ex) *Ex {
   127  				return Expr(ex.Query(), ex.Args()...).Ex(context.Background())
   128  			}
   129  
   130  			b.Run("clone", func(b *testing.B) {
   131  				ex := rawBuild()
   132  
   133  				for i := 0; i < b.N; i++ {
   134  					_ = clone(ex)
   135  				}
   136  			})
   137  		})
   138  
   139  		b.Run("IsNilExpr", func(b *testing.B) {
   140  			for i := 0; i < b.N; i++ {
   141  				IsNilExpr(Expr(`#ID > ?`, 1))
   142  			}
   143  		})
   144  
   145  		b.Run("by expr", func(b *testing.B) {
   146  			for i := 0; i < b.N; i++ {
   147  				e := And(
   148  					TypedCol[int]("f_id").V(Lt(1)),
   149  					TypedCol[int]("f_id").V(In(1, 2, 3)),
   150  				)
   151  				e.Ex(context.Background())
   152  			}
   153  		})
   154  
   155  		b.Run("by expr without re created", func(b *testing.B) {
   156  			fid := TypedCol[int]("f_id")
   157  			left := fid.V(Lt(0))
   158  			right := fid.V(In(1, 2, 3))
   159  
   160  			b.Run("single", func(b *testing.B) {
   161  				for i := 0; i < b.N; i++ {
   162  					left.Ex(context.Background())
   163  				}
   164  			})
   165  
   166  			b.Run("composed", func(b *testing.B) {
   167  				e := And(left, left, right, right)
   168  
   169  				b.Log(e.Ex(context.Background()).Query())
   170  
   171  				for i := 0; i < b.N; i++ {
   172  					e.Ex(context.Background())
   173  				}
   174  			})
   175  		})
   176  	})
   177  }
   178  
   179  type Point struct {
   180  	X float64
   181  	Y float64
   182  }
   183  
   184  func (Point) DataType(engine string) string {
   185  	return "POINT"
   186  }
   187  
   188  func (Point) ValueEx() string {
   189  	return `ST_GeomFromText(?)`
   190  }
   191  
   192  func (p Point) Value() (driver.Value, error) {
   193  	return fmt.Sprintf("POINT(%v %v)", p.X, p.Y), nil
   194  }