github.com/dolthub/go-mysql-server@v0.18.0/sql/in_mem_table/multimapeditors_test.go (about) 1 // Copyright 2023 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package in_mem_table 16 17 import ( 18 "errors" 19 "testing" 20 21 "github.com/stretchr/testify/require" 22 23 "github.com/dolthub/go-mysql-server/sql" 24 ) 25 26 var userValueOps = ValueOps[*user]{ 27 ToRow: func(ctx *sql.Context, u *user) (sql.Row, error) { 28 return sql.Row{u.username, u.email}, nil 29 }, 30 FromRow: func(ctx *sql.Context, r sql.Row) (*user, error) { 31 if len(r) != 2 { 32 return nil, errors.New("invalid schema for user insert") 33 } 34 username, ok := r[0].(string) 35 if !ok { 36 return nil, errors.New("invalid schema for user insert") 37 } 38 email, ok := r[1].(string) 39 if !ok { 40 return nil, errors.New("invalid schema for user insert") 41 } 42 return &user{username, email, 0}, nil 43 }, 44 UpdateWithRow: func(ctx *sql.Context, r sql.Row, u *user) (*user, error) { 45 if len(r) != 2 { 46 return nil, errors.New("invalid schema for user insert") 47 } 48 username, ok := r[0].(string) 49 if !ok { 50 return nil, errors.New("invalid schema for user insert") 51 } 52 email, ok := r[1].(string) 53 if !ok { 54 return nil, errors.New("invalid schema for user insert") 55 } 56 uu := *u 57 uu.username = username 58 uu.email = email 59 return &uu, nil 60 }, 61 } 62 63 func TestTableEditorInsert(t *testing.T) { 64 t.Run("InsertRow", func(t *testing.T) { 65 set := NewIndexedSet(ueq, keyers) 66 ed := &IndexedSetTableEditor[*user]{set, userValueOps} 67 ed.StatementBegin(nil) 68 require.NoError(t, ed.Insert(nil, sql.Row{"aaron", "aaron@dolthub.com"})) 69 require.NoError(t, ed.StatementComplete(nil)) 70 require.Equal(t, 1, set.Count()) 71 require.Len(t, set.GetMany(usernameKeyer{}, "aaron"), 1) 72 }) 73 t.Run("InsertDuplicateRow", func(t *testing.T) { 74 set := NewIndexedSet(ueq, keyers) 75 ed := &IndexedSetTableEditor[*user]{set, userValueOps} 76 ed.StatementBegin(nil) 77 require.NoError(t, ed.Insert(nil, sql.Row{"aaron", "aaron@dolthub.com"})) 78 require.Error(t, ed.Insert(nil, sql.Row{"aaron", "aaron@dolthub.com"})) 79 }) 80 t.Run("InsertBadSchema", func(t *testing.T) { 81 set := NewIndexedSet(ueq, keyers) 82 ed := &IndexedSetTableEditor[*user]{set, userValueOps} 83 ed.StatementBegin(nil) 84 require.Error(t, ed.Insert(nil, sql.Row{"aaron", "aaron@dolthub.com", "extra value"})) 85 require.Error(t, ed.Insert(nil, sql.Row{123, "aaron@dolthub.com"})) 86 require.Error(t, ed.Insert(nil, sql.Row{"aaron", 123})) 87 }) 88 } 89 90 func TestTableEditorDelete(t *testing.T) { 91 t.Run("DeleteRow", func(t *testing.T) { 92 set := NewIndexedSet(ueq, keyers) 93 set.Put(&user{"aaron", "aaron@dolthub.com", 0}) 94 set.Put(&user{"brian", "brian@dolthub.com", 0}) 95 ed := &IndexedSetTableEditor[*user]{set, userValueOps} 96 ed.StatementBegin(nil) 97 require.NoError(t, ed.Delete(nil, sql.Row{"aaron", "aaron@dolthub.com"})) 98 require.NoError(t, ed.StatementComplete(nil)) 99 require.Equal(t, 1, set.Count()) 100 require.Len(t, set.GetMany(usernameKeyer{}, "brian"), 1) 101 }) 102 t.Run("NoMatch", func(t *testing.T) { 103 set := NewIndexedSet(ueq, keyers) 104 set.Put(&user{"aaron", "aaron@dolthub.com", 0}) 105 set.Put(&user{"brian", "brian@dolthub.com", 0}) 106 ed := &IndexedSetTableEditor[*user]{set, userValueOps} 107 ed.StatementBegin(nil) 108 require.NoError(t, ed.Delete(nil, sql.Row{"jason", "jason@dolthub.com"})) 109 require.NoError(t, ed.StatementComplete(nil)) 110 require.Equal(t, 2, set.Count()) 111 require.Len(t, set.GetMany(usernameKeyer{}, "brian"), 1) 112 require.Len(t, set.GetMany(usernameKeyer{}, "aaron"), 1) 113 }) 114 t.Run("OnlyConsidersPrimaryKey", func(t *testing.T) { 115 set := NewIndexedSet(ueq, keyers) 116 set.Put(&user{"aaron", "aaron@dolthub.com", 0}) 117 set.Put(&user{"brian", "brian@dolthub.com", 0}) 118 ed := &IndexedSetTableEditor[*user]{set, userValueOps} 119 ed.StatementBegin(nil) 120 require.NoError(t, ed.Delete(nil, sql.Row{"aaron", "aaron+alternative@dolthub.com"})) 121 require.NoError(t, ed.StatementComplete(nil)) 122 require.Equal(t, 1, set.Count()) 123 require.Len(t, set.GetMany(usernameKeyer{}, "brian"), 1) 124 require.Len(t, set.GetMany(usernameKeyer{}, "aaron"), 0) 125 }) 126 } 127 128 func TestTableEditorUpdate(t *testing.T) { 129 t.Run("UpdateNonPrimary", func(t *testing.T) { 130 set := NewIndexedSet(ueq, keyers) 131 set.Put(&user{"aaron", "aaron@dolthub.com", 0}) 132 set.Put(&user{"brian", "brian@dolthub.com", 0}) 133 ed := &IndexedSetTableEditor[*user]{set, userValueOps} 134 ed.StatementBegin(nil) 135 require.NoError(t, ed.Update(nil, sql.Row{"aaron", "aaron@dolthub.com"}, sql.Row{"aaron", "aaron+new@dolthub.com"})) 136 require.NoError(t, ed.StatementComplete(nil)) 137 require.Equal(t, 2, set.Count()) 138 require.Len(t, set.GetMany(usernameKeyer{}, "brian"), 1) 139 require.Len(t, set.GetMany(usernameKeyer{}, "aaron"), 1) 140 require.Len(t, set.GetMany(emailKeyer{}, "aaron+new@dolthub.com"), 1) 141 require.Len(t, set.GetMany(emailKeyer{}, "aaron@dolthub.com"), 0) 142 }) 143 t.Run("UpdatePrimary", func(t *testing.T) { 144 set := NewIndexedSet(ueq, keyers) 145 set.Put(&user{"aaron", "aaron@dolthub.com", 0}) 146 set.Put(&user{"brian", "brian@dolthub.com", 0}) 147 ed := &IndexedSetTableEditor[*user]{set, userValueOps} 148 ed.StatementBegin(nil) 149 require.NoError(t, ed.Update(nil, sql.Row{"aaron", "aaron@dolthub.com"}, sql.Row{"aaron.son", "aaron+new@dolthub.com"})) 150 require.NoError(t, ed.StatementComplete(nil)) 151 require.Equal(t, 2, set.Count()) 152 require.Len(t, set.GetMany(usernameKeyer{}, "brian"), 1) 153 require.Len(t, set.GetMany(usernameKeyer{}, "aaron.son"), 1) 154 require.Len(t, set.GetMany(usernameKeyer{}, "aaron"), 0) 155 require.Len(t, set.GetMany(emailKeyer{}, "aaron+new@dolthub.com"), 1) 156 require.Len(t, set.GetMany(emailKeyer{}, "aaron@dolthub.com"), 0) 157 }) 158 t.Run("UpdatePreserveSidecar", func(t *testing.T) { 159 // Here we assert that, assuming the UpdateWithRow function is written correctly, 160 // the updated entity which is in the IndexedSet has an opportunity to retain 161 // data which isn't reflected in the mapping from struct <-> sql.Row. 162 163 set := NewIndexedSet(ueq, keyers) 164 set.Put(&user{"aaron", "aaron@dolthub.com", 0}) 165 set.Put(&user{"brian", "brian@dolthub.com", 1}) 166 ed := &IndexedSetTableEditor[*user]{set, userValueOps} 167 ed.StatementBegin(nil) 168 require.NoError(t, ed.Update(nil, sql.Row{"brian", "brian@dolthub.com"}, sql.Row{"brian", "brian+new@dolthub.com"})) 169 require.NoError(t, ed.StatementComplete(nil)) 170 require.Equal(t, 2, set.Count()) 171 res := set.GetMany(usernameKeyer{}, "brian") 172 require.Len(t, res, 1) 173 require.Equal(t, 1, res[0].sidecar) 174 }) 175 } 176 177 const ( 178 userPetDog = 1 179 userPetCat = 1 << 1 180 userPetFish = 1 << 2 181 ) 182 183 var userPetsMultiValueOps = MultiValueOps[*user]{ 184 ToRows: func(ctx *sql.Context, u *user) ([]sql.Row, error) { 185 var res []sql.Row 186 if (u.sidecar & userPetDog) == userPetDog { 187 res = append(res, sql.Row{u.username, u.email, "dog"}) 188 } 189 if (u.sidecar & userPetCat) == userPetCat { 190 res = append(res, sql.Row{u.username, u.email, "cat"}) 191 } 192 if (u.sidecar & userPetFish) == userPetFish { 193 res = append(res, sql.Row{u.username, u.email, "fish"}) 194 } 195 return res, nil 196 }, 197 FromRow: func(ctx *sql.Context, r sql.Row) (*user, error) { 198 if len(r) != 3 { 199 return nil, errors.New("invalid schema for user pet insert") 200 } 201 username, ok := r[0].(string) 202 if !ok { 203 return nil, errors.New("invalid schema for user pet insert") 204 } 205 email, ok := r[1].(string) 206 if !ok { 207 return nil, errors.New("invalid schema for user pet insert") 208 } 209 pet, ok := r[2].(string) 210 if !ok { 211 return nil, errors.New("invalid schema for user pet insert") 212 } 213 var sidecar int 214 if pet == "dog" { 215 sidecar = userPetDog 216 } else if pet == "cat" { 217 sidecar = userPetCat 218 } else if pet == "fish" { 219 sidecar = userPetFish 220 } else { 221 return nil, errors.New("invalid schema for user pet insert") 222 } 223 return &user{username, email, sidecar}, nil 224 }, 225 AddRow: func(ctx *sql.Context, r sql.Row, u *user) (*user, error) { 226 if len(r) != 3 { 227 return nil, errors.New("invalid schema for user pet insert") 228 } 229 pet, ok := r[2].(string) 230 if !ok { 231 return nil, errors.New("invalid schema for user pet insert") 232 } 233 var sidecar int 234 if pet == "dog" { 235 sidecar = userPetDog 236 } else if pet == "cat" { 237 sidecar = userPetCat 238 } else if pet == "fish" { 239 sidecar = userPetFish 240 } else { 241 return nil, errors.New("invalid schema for user pet insert") 242 } 243 uu := *u 244 uu.sidecar |= sidecar 245 return &uu, nil 246 }, 247 DeleteRow: func(ctx *sql.Context, r sql.Row, u *user) (*user, error) { 248 if len(r) != 3 { 249 return nil, errors.New("invalid schema for user pet insert") 250 } 251 pet, ok := r[2].(string) 252 if !ok { 253 return nil, errors.New("invalid schema for user pet insert") 254 } 255 var sidecar int 256 if pet == "dog" { 257 sidecar = userPetDog 258 } else if pet == "cat" { 259 sidecar = userPetCat 260 } else if pet == "fish" { 261 sidecar = userPetFish 262 } else { 263 return nil, errors.New("invalid schema for user pet insert") 264 } 265 uu := *u 266 uu.sidecar &= ^sidecar 267 return &uu, nil 268 }, 269 } 270 271 func TestMultiTableEditorInsert(t *testing.T) { 272 t.Run("InsertRow", func(t *testing.T) { 273 set := NewIndexedSet(ueq, keyers) 274 set.Put(&user{"aaron", "aaron@dolthub.com", 0}) 275 ed := &MultiIndexedSetTableEditor[*user]{set, userPetsMultiValueOps} 276 ed.StatementBegin(nil) 277 require.NoError(t, ed.Insert(nil, sql.Row{"aaron", "aaron@dolthub.com", "dog"})) 278 require.NoError(t, ed.Insert(nil, sql.Row{"aaron", "aaron@dolthub.com", "fish"})) 279 require.NoError(t, ed.StatementComplete(nil)) 280 require.Equal(t, 1, set.Count()) 281 res := set.GetMany(usernameKeyer{}, "aaron") 282 require.Len(t, res, 1) 283 require.Equal(t, userPetDog|userPetFish, res[0].sidecar) 284 }) 285 } 286 287 func TestMultiTableEditorDelete(t *testing.T) { 288 t.Run("DeleteRow", func(t *testing.T) { 289 set := NewIndexedSet(ueq, keyers) 290 set.Put(&user{"aaron", "aaron@dolthub.com", userPetDog | userPetFish}) 291 ed := &MultiIndexedSetTableEditor[*user]{set, userPetsMultiValueOps} 292 ed.StatementBegin(nil) 293 require.NoError(t, ed.Delete(nil, sql.Row{"aaron", "aaron@dolthub.com", "fish"})) 294 require.NoError(t, ed.Delete(nil, sql.Row{"aaron", "aaron@dolthub.com", "cat"})) 295 require.NoError(t, ed.StatementComplete(nil)) 296 require.Equal(t, 1, set.Count()) 297 res := set.GetMany(usernameKeyer{}, "aaron") 298 require.Len(t, res, 1) 299 require.Equal(t, userPetDog, res[0].sidecar) 300 }) 301 } 302 303 func TestMultiTableEditorUpdate(t *testing.T) { 304 t.Run("UpdateRow", func(t *testing.T) { 305 // This test demonstrates that the multi table editor does not edit 306 // data on the entity which is not "owned" by the table. 307 // 308 // TODO: Investigate whether this results in non-compliant 309 // behavior with regards to how the grant tables interact with 310 // UPDATE statements. 311 312 set := NewIndexedSet(ueq, keyers) 313 set.Put(&user{"aaron", "aaron@dolthub.com", userPetDog | userPetFish}) 314 ed := &MultiIndexedSetTableEditor[*user]{set, userPetsMultiValueOps} 315 ed.StatementBegin(nil) 316 require.NoError(t, ed.Update(nil, sql.Row{"aaron", "aaron@dolthub.com", "dog"}, sql.Row{"aaron", "aaron+new@dolthub.com", "cat"})) 317 require.NoError(t, ed.StatementComplete(nil)) 318 require.Equal(t, 1, set.Count()) 319 res := set.GetMany(usernameKeyer{}, "aaron") 320 require.Len(t, res, 1) 321 require.Equal(t, userPetFish|userPetCat, res[0].sidecar) 322 323 // Here we see the email address was _not_ updated. 324 require.Equal(t, "aaron@dolthub.com", res[0].email) 325 326 // Relatedly, here we can see that we throw an error if we try 327 // to update a primary key to something that does not already 328 // exist. 329 set = NewIndexedSet(ueq, keyers) 330 set.Put(&user{"aaron", "aaron@dolthub.com", userPetDog | userPetFish}) 331 ed = &MultiIndexedSetTableEditor[*user]{set, userPetsMultiValueOps} 332 ed.StatementBegin(nil) 333 require.Error(t, ed.Update(nil, sql.Row{"aaron", "aaron@dolthub.com", "dog"}, sql.Row{"aaron.son", "aaron@dolthub.com", "cat"})) 334 335 // And we simply update the matching entry if we try to change 336 // the primary key to something that does exist. 337 set = NewIndexedSet(ueq, keyers) 338 set.Put(&user{"aaron", "aaron@dolthub.com", userPetDog | userPetCat}) 339 set.Put(&user{"brian", "brian@dolthub.com", userPetDog}) 340 ed = &MultiIndexedSetTableEditor[*user]{set, userPetsMultiValueOps} 341 ed.StatementBegin(nil) 342 require.NoError(t, ed.Update(nil, sql.Row{"aaron", "aaron@dolthub.com", "dog"}, sql.Row{"brian", "brian@dolthub.com", "cat"})) 343 require.NoError(t, ed.StatementComplete(nil)) 344 require.Equal(t, 2, set.Count()) 345 res = set.GetMany(usernameKeyer{}, "aaron") 346 require.Len(t, res, 1) 347 require.Equal(t, userPetCat, res[0].sidecar) 348 res = set.GetMany(usernameKeyer{}, "brian") 349 require.Len(t, res, 1) 350 require.Equal(t, userPetCat|userPetDog, res[0].sidecar) 351 }) 352 }