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 }