Files
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

420 lines
13 KiB
Go

// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package schema
import (
"fmt"
"reflect"
strings "strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo"
"github.com/dolthub/dolt/go/store/types"
"github.com/dolthub/dolt/go/store/val"
)
const (
lnColName = "last"
fnColName = "first"
addrColName = "address"
ageColName = "age"
titleColName = "title"
reservedColName = "reserved"
lnColTag = 1
fnColTag = 0
addrColTag = 6
ageColTag = 4
titleColTag = 40
reservedColTag = 50
)
var pkCols = []Column{
{Name: lnColName, Tag: lnColTag, Kind: types.StringKind, IsPartOfPK: true, TypeInfo: typeinfo.StringDefaultType},
{Name: fnColName, Tag: fnColTag, Kind: types.StringKind, IsPartOfPK: true, TypeInfo: typeinfo.StringDefaultType},
}
var nonPkCols = []Column{
{Name: addrColName, Tag: addrColTag, Kind: types.StringKind, TypeInfo: typeinfo.StringDefaultType},
{Name: ageColName, Tag: ageColTag, Kind: types.UintKind, TypeInfo: typeinfo.FromKind(types.UintKind)},
{Name: titleColName, Tag: titleColTag, Kind: types.StringKind, TypeInfo: typeinfo.StringDefaultType},
{Name: reservedColName, Tag: reservedColTag, Kind: types.StringKind, TypeInfo: typeinfo.StringDefaultType},
}
var allCols = append(append([]Column(nil), pkCols...), nonPkCols...)
func TestNewSchema(t *testing.T) {
allColColl := NewColCollection(allCols...)
pkColColl := NewColCollection()
indexCol := NewIndexCollection(allColColl, pkColColl)
checkCol := NewCheckCollection()
_, err := checkCol.AddCheck("chk_age", "age > 0", true, false)
require.NoError(t, err)
// Nil ordinals
sch, err := NewSchema(allColColl, nil, Collation_Default, indexCol, checkCol)
require.NoError(t, err)
require.Equal(t, []int{0, 1}, sch.GetPkOrdinals())
require.Equal(t, Collation_Default, sch.GetCollation())
require.True(t, sch.Indexes().Equals(indexCol))
require.True(t, sch.Checks().Equals(checkCol))
// Set ordinals explicitly
indexCol.(*indexCollectionImpl).pks = []uint64{fnColTag, lnColTag}
sch, err = NewSchema(allColColl, []int{1, 0}, Collation_Default, indexCol, checkCol)
require.NoError(t, err)
require.Equal(t, []int{1, 0}, sch.GetPkOrdinals())
require.Equal(t, Collation_Default, sch.GetCollation())
require.True(t, sch.Indexes().Equals(indexCol))
require.True(t, sch.Checks().Equals(checkCol))
}
func TestSchema(t *testing.T) {
colColl := NewColCollection(allCols...)
schFromCols, err := SchemaFromCols(colColl)
require.NoError(t, err)
testSchema("SchemaFromCols", schFromCols, t)
testKeyColColl := NewColCollection(pkCols...)
testNonKeyColsColl := NewColCollection(nonPkCols...)
schFromPKAndNonPKCols, _ := SchemaFromPKAndNonPKCols(testKeyColColl, testNonKeyColsColl)
testSchema("SchemaFromPKAndNonPKCols", schFromPKAndNonPKCols, t)
eq := SchemasAreEqual(schFromCols, schFromPKAndNonPKCols)
assert.True(t, eq, "schemas should be equal")
}
func TestSchemaWithNoPKs(t *testing.T) {
colColl := NewColCollection(nonPkCols...)
_, _ = SchemaFromCols(colColl)
assert.NotPanics(t, func() {
UnkeyedSchemaFromCols(colColl)
})
}
func TestGetSharedCols(t *testing.T) {
colColl := NewColCollection(nonPkCols...)
sch, _ := SchemaFromCols(colColl)
names := []string{addrColName, ageColName}
kinds := []types.NomsKind{types.StringKind, types.UintKind}
res := GetSharedCols(sch, names, kinds)
expected := []Column{
mustGetCol(colColl, addrColName),
mustGetCol(colColl, ageColName),
}
assert.Equal(t, expected, res)
}
func TestSetPkOrder(t *testing.T) {
// GetPkCols() should always return columns in ordinal order
// GetAllCols() should always return columns in the defined schema's order
t.Run("returns the correct GetPkCols() order", func(t *testing.T) {
allColColl := NewColCollection(allCols...)
pkColColl := NewColCollection(pkCols...)
sch, err := SchemaFromCols(allColColl)
require.NoError(t, err)
require.Equal(t, allColColl, sch.GetAllCols())
require.Equal(t, pkColColl, sch.GetPKCols())
err = sch.SetPkOrdinals([]int{1, 0})
require.NoError(t, err)
expectedPkColColl := NewColCollection(pkCols[1], pkCols[0])
require.Equal(t, expectedPkColColl, sch.GetPKCols())
require.Equal(t, allColColl, sch.GetAllCols())
})
t.Run("Can round-trip", func(t *testing.T) {
allColColl := NewColCollection(allCols...)
pkColColl := NewColCollection(pkCols...)
sch, err := SchemaFromCols(allColColl)
require.NoError(t, err)
require.Equal(t, allColColl, sch.GetAllCols())
require.Equal(t, pkColColl, sch.GetPKCols())
err = sch.SetPkOrdinals([]int{1, 0})
require.NoError(t, err)
err = sch.SetPkOrdinals([]int{0, 1})
require.NoError(t, err)
require.Equal(t, allColColl, sch.GetAllCols())
require.Equal(t, pkColColl, sch.GetPKCols())
})
}
func mustGetCol(collection *ColCollection, name string) Column {
col, ok := collection.GetByName(name)
if !ok {
panic(fmt.Sprintf("%s not found", name))
}
return col
}
func TestIsKeyless(t *testing.T) {
cc := NewColCollection(allCols...)
pkSch, err := SchemaFromCols(cc)
require.NoError(t, err)
ok := IsKeyless(pkSch)
assert.False(t, ok)
cc = NewColCollection(nonPkCols...)
keylessSch, err := SchemaFromCols(cc)
assert.NoError(t, err)
ok = IsKeyless(keylessSch)
assert.True(t, ok)
}
func TestValidateForInsert(t *testing.T) {
t.Run("Validate good", func(t *testing.T) {
colColl := NewColCollection(allCols...)
assert.NoError(t, ValidateForInsert(colColl))
})
t.Run("Name collision", func(t *testing.T) {
cols := append(allCols, Column{Name: titleColName, Tag: 100, Kind: types.StringKind, TypeInfo: typeinfo.StringDefaultType})
colColl := NewColCollection(cols...)
err := ValidateForInsert(colColl)
assert.Error(t, err)
assert.Equal(t, err, ErrColNameCollision)
})
t.Run("Case insensitive collision", func(t *testing.T) {
cols := append(allCols, Column{Name: strings.ToUpper(titleColName), Tag: 100, Kind: types.StringKind, TypeInfo: typeinfo.StringDefaultType})
colColl := NewColCollection(cols...)
err := ValidateForInsert(colColl)
assert.Error(t, err)
assert.Equal(t, err, ErrColNameCollision)
})
t.Run("Tag collision", func(t *testing.T) {
cols := append(allCols, Column{Name: "newCol", Tag: lnColTag, Kind: types.StringKind, TypeInfo: typeinfo.StringDefaultType})
colColl := NewColCollection(cols...)
err := ValidateForInsert(colColl)
assert.Error(t, err)
assert.Equal(t, err, ErrColTagCollision)
})
t.Run("AutoIncrement on integer types should be allowed", func(t *testing.T) {
// Test that auto_increment validation is no longer enforced at database layer
// The validation should now be handled by the go-mysql-server layer
// Only integer types should be allowed per MySQL 8.4.5 behavior
testCases := []struct {
name string
kind types.NomsKind
typeInfo typeinfo.TypeInfo
}{
{"INT", types.IntKind, typeinfo.FromKind(types.IntKind)},
{"UINT", types.UintKind, typeinfo.FromKind(types.UintKind)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
autoIncCol := Column{
Name: "auto_inc_col",
Tag: 999,
Kind: tc.kind,
IsPartOfPK: true,
AutoIncrement: true,
TypeInfo: tc.typeInfo,
}
cols := []Column{autoIncCol}
colColl := NewColCollection(cols...)
// Should not error - validation moved to GMS layer
err := ValidateForInsert(colColl)
assert.NoError(t, err, "auto_increment validation should not be enforced at database layer for %s", tc.name)
})
}
})
}
func TestArePrimaryKeySetsDiffable(t *testing.T) {
tests := []struct {
Name string
From Schema
To Schema
Diffable bool
KeyMap val.OrdinalMapping
}{
{
Name: "Basic",
From: MustSchemaFromCols(NewColCollection(
NewColumn("pk", 0, types.IntKind, true))),
To: MustSchemaFromCols(NewColCollection(
NewColumn("pk", 0, types.IntKind, true))),
Diffable: true,
KeyMap: val.OrdinalMapping{0},
},
{
Name: "PK-Column renames",
From: MustSchemaFromCols(NewColCollection(
NewColumn("pk", 1, types.IntKind, true))),
To: MustSchemaFromCols(NewColCollection(
NewColumn("pk2", 1, types.IntKind, true))),
Diffable: true,
KeyMap: val.OrdinalMapping{0},
},
{
Name: "Only pk ordering should matter for diffability",
From: MustSchemaFromCols(NewColCollection(
NewColumn("col1", 0, types.IntKind, false),
NewColumn("pk", 1, types.IntKind, true))),
To: MustSchemaFromCols(NewColCollection(
NewColumn("pk", 1, types.IntKind, true))),
Diffable: true,
KeyMap: val.OrdinalMapping{0},
},
{
Name: "Only pk ordering should matter for diffability - inverse",
From: MustSchemaFromCols(NewColCollection(
NewColumn("pk", 1, types.IntKind, true))),
To: MustSchemaFromCols(NewColCollection(
NewColumn("col1", 2, types.IntKind, false),
NewColumn("pk", 1, types.IntKind, true))),
Diffable: true,
KeyMap: val.OrdinalMapping{0},
},
{
Name: "Only pk ordering should matter for diffability - compound",
From: MustSchemaFromCols(NewColCollection(
NewColumn("pk1", 0, types.IntKind, true),
NewColumn("col1", 1, types.IntKind, false),
NewColumn("pk2", 2, types.IntKind, true))),
To: MustSchemaFromCols(NewColCollection(
NewColumn("pk1", 0, types.IntKind, true),
NewColumn("pk2", 2, types.IntKind, true))),
Diffable: true,
KeyMap: val.OrdinalMapping{0, 1},
},
{
Name: "Tag mismatches",
From: MustSchemaFromCols(NewColCollection(
NewColumn("pk", 0, types.IntKind, true))),
To: MustSchemaFromCols(NewColCollection(
NewColumn("pk", 1, types.IntKind, true))),
Diffable: false,
},
{
Name: "PK Ordinal mismatches",
From: MustSchemaFromCols(NewColCollection(
NewColumn("pk1", 0, types.IntKind, true),
NewColumn("pk2", 1, types.IntKind, true))),
To: MustSchemaFromCols(NewColCollection(
NewColumn("pk2", 1, types.IntKind, true),
NewColumn("pk1", 0, types.IntKind, true))),
Diffable: false,
},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
d := ArePrimaryKeySetsDiffable(test.From, test.To)
require.Equal(t, test.Diffable, d)
// If they are diffable then we should be able to map their schemas from one to another.
if d {
keyMap, _, err := MapSchemaBasedOnTagAndName(test.From, test.To)
require.NoError(t, err)
require.Equal(t, test.KeyMap, keyMap)
}
})
}
}
func TestArePrimaryKeySetsDiffableTypeChanges(t *testing.T) {
// New format compares underlying SQL types
tests := []struct {
Name string
From Schema
To Schema
Diffable bool
}{
{
Name: "Int -> String (New Format)",
From: MustSchemaFromCols(NewColCollection(
NewColumn("pk", 0, types.IntKind, true))),
To: MustSchemaFromCols(NewColCollection(
NewColumn("pk", 0, types.StringKind, true))),
Diffable: false,
},
}
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
d := ArePrimaryKeySetsDiffable(test.From, test.To)
require.Equal(t, test.Diffable, d)
})
}
}
func testSchema(method string, sch Schema, t *testing.T) {
validateCols(t, allCols, sch.GetAllCols(), method+"GetAllCols")
validateCols(t, pkCols, sch.GetPKCols(), method+"GetPKCols")
validateCols(t, nonPkCols, sch.GetNonPKCols(), method+"GetNonPKCols")
extracted, err := ExtractAllColNames(sch)
assert.NoError(t, err)
expExt := map[uint64]string{
lnColTag: lnColName,
fnColTag: fnColName,
ageColTag: ageColName,
addrColTag: addrColName,
titleColTag: titleColName,
reservedColTag: reservedColName,
}
if !reflect.DeepEqual(extracted, expExt) {
t.Error("extracted columns did not match expectation")
}
if col, ok := ColFromName(sch, titleColName); !ok {
t.Error("Failed to get by name")
} else if col.Tag != titleColTag {
t.Error("Unexpected tag")
}
if col, ok := ColFromTag(sch, titleColTag); !ok {
t.Error("Failed to get by name")
} else if col.Name != titleColName {
t.Error("Unexpected tag")
}
}
func validateCols(t *testing.T, cols []Column, colColl *ColCollection, msg string) {
if !reflect.DeepEqual(cols, colColl.cols) {
t.Error()
}
}