github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/doltdb/doltdb_test.go (about) 1 // Copyright 2019 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 doltdb 16 17 import ( 18 "context" 19 "os" 20 "path/filepath" 21 "testing" 22 23 "github.com/google/uuid" 24 "github.com/stretchr/testify/assert" 25 "github.com/stretchr/testify/require" 26 27 "github.com/dolthub/dolt/go/libraries/doltcore/dbfactory" 28 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable" 29 "github.com/dolthub/dolt/go/libraries/doltcore/ref" 30 "github.com/dolthub/dolt/go/libraries/doltcore/row" 31 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 32 "github.com/dolthub/dolt/go/libraries/utils/filesys" 33 "github.com/dolthub/dolt/go/libraries/utils/test" 34 "github.com/dolthub/dolt/go/store/datas" 35 "github.com/dolthub/dolt/go/store/hash" 36 "github.com/dolthub/dolt/go/store/prolly/tree" 37 "github.com/dolthub/dolt/go/store/types" 38 ) 39 40 const ( 41 idTag = 0 42 firstTag = 1 43 lastTag = 2 44 isMarriedTag = 3 45 ageTag = 4 46 emptyTag = 5 47 ) 48 const testSchemaIndexName = "idx_name" 49 const testSchemaIndexAge = "idx_age" 50 51 var id0, _ = uuid.NewRandom() 52 var id1, _ = uuid.NewRandom() 53 var id2, _ = uuid.NewRandom() 54 var id3, _ = uuid.NewRandom() 55 56 func createTestSchema(t *testing.T) schema.Schema { 57 colColl := schema.NewColCollection( 58 schema.NewColumn("id", idTag, types.UUIDKind, true, schema.NotNullConstraint{}), 59 schema.NewColumn("first", firstTag, types.StringKind, false, schema.NotNullConstraint{}), 60 schema.NewColumn("last", lastTag, types.StringKind, false, schema.NotNullConstraint{}), 61 schema.NewColumn("is_married", isMarriedTag, types.BoolKind, false), 62 schema.NewColumn("age", ageTag, types.UintKind, false), 63 schema.NewColumn("empty", emptyTag, types.IntKind, false), 64 ) 65 sch, err := schema.SchemaFromCols(colColl) 66 require.NoError(t, err) 67 _, err = sch.Indexes().AddIndexByColTags(testSchemaIndexName, []uint64{firstTag, lastTag}, nil, schema.IndexProperties{IsUnique: false, Comment: ""}) 68 require.NoError(t, err) 69 _, err = sch.Indexes().AddIndexByColTags(testSchemaIndexAge, []uint64{ageTag}, nil, schema.IndexProperties{IsUnique: false, Comment: ""}) 70 require.NoError(t, err) 71 return sch 72 } 73 74 func CreateTestTable(vrw types.ValueReadWriter, ns tree.NodeStore, tSchema schema.Schema, rowData durable.Index) (*Table, error) { 75 tbl, err := NewTable(context.Background(), vrw, ns, tSchema, rowData, nil, nil) 76 77 if err != nil { 78 return nil, err 79 } 80 81 return tbl, nil 82 } 83 84 func createTestRowData(t *testing.T, vrw types.ValueReadWriter, ns tree.NodeStore, sch schema.Schema) durable.Index { 85 if types.Format_Default == types.Format_DOLT { 86 idx, err := durable.NewEmptyIndex(context.Background(), vrw, ns, sch) 87 require.NoError(t, err) 88 return idx 89 } 90 91 vals := []row.TaggedValues{ 92 {idTag: types.UUID(id0), firstTag: types.String("bill"), lastTag: types.String("billerson"), ageTag: types.Uint(53)}, 93 {idTag: types.UUID(id1), firstTag: types.String("eric"), lastTag: types.String("ericson"), isMarriedTag: types.Bool(true), ageTag: types.Uint(21)}, 94 {idTag: types.UUID(id2), firstTag: types.String("john"), lastTag: types.String("johnson"), isMarriedTag: types.Bool(false), ageTag: types.Uint(53)}, 95 {idTag: types.UUID(id3), firstTag: types.String("robert"), lastTag: types.String("robertson"), ageTag: types.Uint(36)}, 96 } 97 98 var err error 99 rows := make([]row.Row, len(vals)) 100 101 m, err := types.NewMap(context.Background(), vrw) 102 assert.NoError(t, err) 103 ed := m.Edit() 104 105 for i, val := range vals { 106 r, err := row.New(vrw.Format(), sch, val) 107 require.NoError(t, err) 108 rows[i] = r 109 ed = ed.Set(r.NomsMapKey(sch), r.NomsMapValue(sch)) 110 } 111 112 m, err = ed.Map(context.Background()) 113 assert.NoError(t, err) 114 return durable.IndexFromNomsMap(m, vrw, ns) 115 } 116 117 func TestIsValidTableName(t *testing.T) { 118 assert.True(t, IsValidTableName("a")) 119 assert.True(t, IsValidTableName("a1")) 120 assert.True(t, IsValidTableName("_a1")) 121 assert.True(t, IsValidTableName("a1_b_c------1")) 122 assert.True(t, IsValidTableName("Add-098234_lkjasdf0p98")) 123 assert.True(t, IsValidTableName("1")) 124 assert.True(t, IsValidTableName("-")) 125 assert.True(t, IsValidTableName("-a")) 126 assert.True(t, IsValidTableName("a1-")) 127 assert.True(t, IsValidTableName("ab!!c")) 128 assert.True(t, IsValidTableName(" space")) 129 assert.True(t, IsValidTableName("this is ok")) 130 assert.True(t, IsValidTableName(" ~!@#$%^&*()_+`-=[]{}|;':\",./<>?")) 131 assert.True(t, IsValidTableName("あえいおう")) 132 assert.True(t, IsValidTableName("might/be/problematic")) 133 assert.False(t, IsValidTableName("")) 134 assert.False(t, IsValidTableName(string(rune(0)))) 135 assert.False(t, IsValidTableName("𐌃𐌏𐌋𐌕")) 136 assert.False(t, IsValidTableName("space ")) 137 } 138 139 // DO NOT CHANGE THIS TEST 140 // It is necessary to ensure consistent system table definitions 141 // for more info: https://github.com/dolthub/dolt/pull/663 142 func TestSystemTableTags(t *testing.T) { 143 var sysTableMin uint64 = 1 << 51 144 145 t.Run("asdf", func(t *testing.T) { 146 assert.Equal(t, sysTableMin, schema.SystemTableReservedMin) 147 }) 148 t.Run("dolt_doc tags", func(t *testing.T) { 149 docTableMin := sysTableMin + uint64(5) 150 assert.Equal(t, docTableMin+0, schema.DocNameTag) 151 assert.Equal(t, docTableMin+1, schema.DocTextTag) 152 }) 153 t.Run("dolt_history_ tags", func(t *testing.T) { 154 doltHistoryMin := sysTableMin + uint64(1000) 155 assert.Equal(t, doltHistoryMin+0, schema.HistoryCommitterTag) 156 assert.Equal(t, doltHistoryMin+1, schema.HistoryCommitHashTag) 157 assert.Equal(t, doltHistoryMin+2, schema.HistoryCommitDateTag) 158 }) 159 t.Run("dolt_diff_ tags", func(t *testing.T) { 160 diffTableMin := sysTableMin + uint64(2000) 161 assert.Equal(t, diffTableMin+0, schema.DiffCommitTag) 162 }) 163 t.Run("dolt_query_catalog tags", func(t *testing.T) { 164 queryCatalogMin := sysTableMin + uint64(3005) 165 assert.Equal(t, queryCatalogMin+0, schema.QueryCatalogIdTag) 166 assert.Equal(t, queryCatalogMin+1, schema.QueryCatalogOrderTag) 167 assert.Equal(t, queryCatalogMin+2, schema.QueryCatalogNameTag) 168 assert.Equal(t, queryCatalogMin+3, schema.QueryCatalogQueryTag) 169 assert.Equal(t, queryCatalogMin+4, schema.QueryCatalogDescriptionTag) 170 }) 171 t.Run("dolt_schemas tags", func(t *testing.T) { 172 doltSchemasMin := sysTableMin + uint64(4007) 173 assert.Equal(t, doltSchemasMin+0, schema.DoltSchemasIdTag) 174 assert.Equal(t, doltSchemasMin+1, schema.DoltSchemasTypeTag) 175 assert.Equal(t, doltSchemasMin+2, schema.DoltSchemasNameTag) 176 assert.Equal(t, doltSchemasMin+3, schema.DoltSchemasFragmentTag) 177 assert.Equal(t, doltSchemasMin+4, schema.DoltSchemasExtraTag) 178 }) 179 t.Run("dolt_conflicts_ tags", func(t *testing.T) { 180 doltConflictsMin := sysTableMin + uint64(7000) 181 assert.Equal(t, doltConflictsMin+0, schema.DoltConflictsOurDiffTypeTag) 182 assert.Equal(t, doltConflictsMin+1, schema.DoltConflictsTheirDiffTypeTag) 183 assert.Equal(t, doltConflictsMin+2, schema.DoltConflictsBaseCardinalityTag) 184 assert.Equal(t, doltConflictsMin+3, schema.DoltConflictsOurCardinalityTag) 185 assert.Equal(t, doltConflictsMin+4, schema.DoltConflictsTheirCardinalityTag) 186 }) 187 } 188 189 func TestEmptyInMemoryRepoCreation(t *testing.T) { 190 ddb, err := LoadDoltDB(context.Background(), types.Format_Default, InMemDoltDB, filesys.LocalFS) 191 192 if err != nil { 193 t.Fatal("Failed to load db") 194 } 195 defer ddb.Close() 196 197 err = ddb.WriteEmptyRepo(context.Background(), "master", "Bill Billerson", "bigbillieb@fake.horse") 198 199 if err != nil { 200 t.Fatal("Unexpected error creating empty repo", err) 201 } 202 203 cs, _ := NewCommitSpec("master") 204 optCmt, err := ddb.Resolve(context.Background(), cs, nil) 205 if err != nil { 206 t.Fatal("Could not find commit") 207 } 208 commit, ok := optCmt.ToCommit() 209 assert.True(t, ok) 210 211 h, err := commit.HashOf() 212 assert.NoError(t, err) 213 cs2, _ := NewCommitSpec(h.String()) 214 _, err = ddb.Resolve(context.Background(), cs2, nil) 215 216 if err != nil { 217 t.Fatal("Failed to get commit by hash") 218 } 219 } 220 221 func TestLoadNonExistentLocalFSRepo(t *testing.T) { 222 _, err := test.ChangeToTestDir("TestLoadRepo") 223 224 if err != nil { 225 panic("Couldn't change the working directory to the test directory.") 226 } 227 228 ddb, err := LoadDoltDB(context.Background(), types.Format_Default, LocalDirDoltDB, filesys.LocalFS) 229 assert.Nil(t, ddb, "Should return nil when loading a non-existent data dir") 230 assert.Error(t, err, "Should see an error here") 231 } 232 233 func TestLoadBadLocalFSRepo(t *testing.T) { 234 testDir, err := test.ChangeToTestDir("TestLoadRepo") 235 236 if err != nil { 237 panic("Couldn't change the working directory to the test directory.") 238 } 239 240 contents := []byte("not a directory") 241 os.WriteFile(filepath.Join(testDir, dbfactory.DoltDataDir), contents, 0644) 242 243 ddb, err := LoadDoltDB(context.Background(), types.Format_Default, LocalDirDoltDB, filesys.LocalFS) 244 assert.Nil(t, ddb, "Should return nil when loading a non-directory data dir file") 245 assert.Error(t, err, "Should see an error here") 246 } 247 248 func TestLDNoms(t *testing.T) { 249 testDir, err := test.ChangeToTestDir("TestLoadRepo") 250 251 if err != nil { 252 panic("Couldn't change the working directory to the test directory.") 253 } 254 255 committerName := "Bill Billerson" 256 committerEmail := "bigbillieb@fake.horse" 257 258 // Create an empty repo in a temp dir on the filesys 259 { 260 err := filesys.LocalFS.MkDirs(filepath.Join(testDir, dbfactory.DoltDataDir)) 261 262 if err != nil { 263 t.Fatal("Failed to create noms directory") 264 } 265 266 ddb, _ := LoadDoltDB(context.Background(), types.Format_Default, LocalDirDoltDB, filesys.LocalFS) 267 err = ddb.WriteEmptyRepo(context.Background(), "master", committerName, committerEmail) 268 269 if err != nil { 270 t.Fatal("Unexpected error creating empty repo", err) 271 } 272 } 273 274 //read the empty repo back and add a new table. Write the value, but don't commit 275 var valHash hash.Hash 276 var tbl *Table 277 { 278 ddb, _ := LoadDoltDB(context.Background(), types.Format_Default, LocalDirDoltDB, filesys.LocalFS) 279 cs, _ := NewCommitSpec("master") 280 optCmt, err := ddb.Resolve(context.Background(), cs, nil) 281 if err != nil { 282 t.Fatal("Couldn't find commit") 283 } 284 commit, ok := optCmt.ToCommit() 285 assert.True(t, ok) 286 287 meta, err := commit.GetCommitMeta(context.Background()) 288 assert.NoError(t, err) 289 290 if meta.Name != committerName || meta.Email != committerEmail { 291 t.Error("Unexpected metadata") 292 } 293 294 root, err := commit.GetRootValue(context.Background()) 295 296 assert.NoError(t, err) 297 298 names, err := root.GetTableNames(context.Background(), DefaultSchemaName) 299 assert.NoError(t, err) 300 if len(names) != 0 { 301 t.Fatal("There should be no tables in empty db") 302 } 303 304 ctx := context.Background() 305 tSchema := createTestSchema(t) 306 rowData, err := durable.NewEmptyIndex(ctx, ddb.vrw, ddb.ns, tSchema) 307 if err != nil { 308 t.Fatal("Failed to create new empty index") 309 } 310 311 tbl, err = CreateTestTable(ddb.vrw, ddb.ns, tSchema, rowData) 312 if err != nil { 313 t.Fatal("Failed to create test table with data") 314 } 315 316 root, err = root.PutTable(context.Background(), TableName{Name: "test"}, tbl) 317 assert.NoError(t, err) 318 319 root, valHash, err = ddb.WriteRootValue(context.Background(), root) 320 assert.NoError(t, err) 321 322 meta, err = datas.NewCommitMeta(committerName, committerEmail, "Sample data") 323 if err != nil { 324 t.Error("Failed to commit") 325 } 326 327 commit, err = ddb.Commit(context.Background(), valHash, ref.NewBranchRef("master"), meta) 328 if err != nil { 329 t.Error("Failed to commit") 330 } 331 332 rootHash, err := ddb.NomsRoot(context.Background()) 333 if err != nil { 334 t.Error("Failed to get root hash") 335 } 336 branches, err := ddb.GetBranchesByRootHash(context.Background(), rootHash) 337 if err != nil { 338 t.Error("Failed to get branches by root hash") 339 } 340 assert.Equal(t, len(branches), 1) 341 assert.Equal(t, branches[0].Ref.GetPath(), "master") 342 343 numParents := commit.NumParents() 344 assert.NoError(t, err) 345 346 if numParents != 1 { 347 t.Error("Unexpected ancestry") 348 } 349 350 root, err = commit.GetRootValue(context.Background()) 351 assert.NoError(t, err) 352 353 readTable, ok, err := root.GetTable(context.Background(), TableName{Name: "test"}) 354 assert.NoError(t, err) 355 356 if !ok { 357 t.Error("Could not retrieve test table") 358 } 359 360 ts, err := tbl.GetSchema(context.Background()) 361 require.NoError(t, err) 362 363 rs, err := readTable.GetSchema(context.Background()) 364 require.NoError(t, err) 365 366 if !schema.SchemasAreEqual(ts, rs) { 367 t.Error("Unexpected schema") 368 } 369 } 370 }