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  }