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  }