|
| 1 | +// Copyright 2025 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 enginetest |
| 16 | + |
| 17 | +import ( |
| 18 | + "context" |
| 19 | + "fmt" |
| 20 | + sqle "github.com/dolthub/go-mysql-server" |
| 21 | + "github.com/dolthub/go-mysql-server/memory" |
| 22 | + "github.com/dolthub/go-mysql-server/sql" |
| 23 | + "github.com/dolthub/go-mysql-server/sql/planbuilder" |
| 24 | + "github.com/dolthub/go-mysql-server/sql/types" |
| 25 | + "github.com/stretchr/testify/require" |
| 26 | + "testing" |
| 27 | +) |
| 28 | + |
| 29 | +// ErrorWrapper is a wrapped type that errors when unwrapped. This can be used to test that certain operations |
| 30 | +// won't trigger an unwrap. |
| 31 | +type ErrorWrapper[T any] struct { |
| 32 | + maxByteLength int64 |
| 33 | + isExactLength bool |
| 34 | +} |
| 35 | + |
| 36 | +func (w ErrorWrapper[T]) Compare(ctx context.Context, other interface{}) (cmp int, comparable bool, err error) { |
| 37 | + return 0, false, nil |
| 38 | +} |
| 39 | + |
| 40 | +var textErrorWrapper = ErrorWrapper[string]{maxByteLength: types.Text.MaxByteLength(), isExactLength: false} |
| 41 | +var longTextErrorWrapper = ErrorWrapper[string]{maxByteLength: types.LongText.MaxByteLength(), isExactLength: false} |
| 42 | + |
| 43 | +func exactLengthErrorWrapper(maxByteLength int64) ErrorWrapper[string] { |
| 44 | + return ErrorWrapper[string]{maxByteLength: maxByteLength, isExactLength: true} |
| 45 | +} |
| 46 | + |
| 47 | +func (w ErrorWrapper[T]) assertInterfaces() { |
| 48 | + var _ sql.Wrapper[T] = w |
| 49 | +} |
| 50 | + |
| 51 | +func (w ErrorWrapper[T]) Unwrap(ctx context.Context) (result T, err error) { |
| 52 | + return result, fmt.Errorf("unwrap failed") |
| 53 | +} |
| 54 | + |
| 55 | +func (w ErrorWrapper[T]) UnwrapAny(ctx context.Context) (result interface{}, err error) { |
| 56 | + return result, fmt.Errorf("unwrap failed") |
| 57 | +} |
| 58 | + |
| 59 | +func (w ErrorWrapper[T]) MaxByteLength() int64 { |
| 60 | + return w.maxByteLength |
| 61 | +} |
| 62 | + |
| 63 | +func (w ErrorWrapper[T]) IsExactLength() bool { |
| 64 | + return w.isExactLength |
| 65 | +} |
| 66 | + |
| 67 | +func setupWrapperTests(t *testing.T) (*sql.Context, *memory.Database, *MemoryHarness, *sqle.Engine) { |
| 68 | + db := memory.NewDatabase("mydb") |
| 69 | + pro := memory.NewDBProvider(db) |
| 70 | + harness := NewDefaultMemoryHarness().WithProvider(pro) |
| 71 | + ctx := NewContext(harness) |
| 72 | + e := NewEngineWithProvider(t, harness, pro) |
| 73 | + return ctx, db, harness, e |
| 74 | +} |
| 75 | + |
| 76 | +// TestWrapperCopyInKey tests that copying a wrapped value in the primary key doesn't require the value to be unwrapped. |
| 77 | +// This is skipped because inserting into tables requires comparisons between primary keys, which currently requires |
| 78 | +// unwrapping. But in the future, we may be able to skip fully unwrapping values for specific operations. |
| 79 | +func TestWrapperCopyInKey(t *testing.T) { |
| 80 | + t.Skip() |
| 81 | + ctx, db, harness, e := setupWrapperTests(t) |
| 82 | + |
| 83 | + schema := sql.NewPrimaryKeySchema(sql.Schema{ |
| 84 | + &sql.Column{Name: "col1", Source: "test", Type: types.LongText, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `""`, types.Text, false)}, |
| 85 | + }) |
| 86 | + table := memory.NewTable(db.BaseDatabase, "test", schema, nil) |
| 87 | + |
| 88 | + require.NoError(t, table.Insert(ctx, sql.Row{"brave"})) |
| 89 | + require.NoError(t, table.Insert(ctx, sql.Row{longTextErrorWrapper})) |
| 90 | + require.NoError(t, table.Insert(ctx, sql.Row{"!"})) |
| 91 | + |
| 92 | + TestQueryWithContext(t, ctx, e, harness, "CREATE TABLE t2 AS SELECT 1, col1, 2 FROM test;", nil, nil, nil, nil) |
| 93 | +} |
| 94 | + |
| 95 | +// TestWrapperCopyInKey tests that copying a wrapped value not in the primary key doesn't require the value to be unwrapped. |
| 96 | +func TestWrapperCopyNotInKey(t *testing.T) { |
| 97 | + ctx, db, harness, e := setupWrapperTests(t) |
| 98 | + |
| 99 | + schema := sql.NewPrimaryKeySchema(sql.Schema{ |
| 100 | + &sql.Column{Name: "pk", Source: "test", Type: types.Int64, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `1`, types.Int64, false), PrimaryKey: true}, |
| 101 | + &sql.Column{Name: "col1", Source: "test", Type: types.LongText, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `""`, types.Text, false), PrimaryKey: false}, |
| 102 | + }) |
| 103 | + |
| 104 | + testTable := memory.NewTable(db.BaseDatabase, "test", schema, nil) |
| 105 | + db.AddTable("test", testTable) |
| 106 | + |
| 107 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(1), "brave"})) |
| 108 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(2), longTextErrorWrapper})) |
| 109 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(3), "!"})) |
| 110 | + |
| 111 | + copySchema := sql.NewPrimaryKeySchema(sql.Schema{ |
| 112 | + &sql.Column{Name: "one", Source: "t2", Type: types.Int64, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `1`, types.Int64, false), PrimaryKey: true}, |
| 113 | + &sql.Column{Name: "pk", Source: "t2", Type: types.Int64, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `1`, types.Int64, false), PrimaryKey: true}, |
| 114 | + &sql.Column{Name: "col1", Source: "t2", Type: types.LongText, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `""`, types.Text, false), PrimaryKey: false}, |
| 115 | + &sql.Column{Name: "two", Source: "t2", Type: types.Int64, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `1`, types.Int64, false), PrimaryKey: false}, |
| 116 | + }) |
| 117 | + |
| 118 | + testTable2 := memory.NewTable(db.BaseDatabase, "t2", copySchema, nil) |
| 119 | + db.AddTable("t2", testTable2) |
| 120 | + |
| 121 | + TestQueryWithContext(t, ctx, e, harness, "INSERT INTO t2 SELECT 1, pk, col1, 2 FROM test;", nil, nil, nil, nil) |
| 122 | +} |
| 123 | + |
| 124 | +// TestWrapperCopyWhenWideningColumn tests that widening a column doesn't cause values to be unwrapped. |
| 125 | +func TestWrapperCopyWhenWideningColumn(t *testing.T) { |
| 126 | + ctx, db, harness, e := setupWrapperTests(t) |
| 127 | + |
| 128 | + schema := sql.NewPrimaryKeySchema(sql.Schema{ |
| 129 | + &sql.Column{Name: "pk", Source: "test", Type: types.Int64, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `1`, types.Int64, false), PrimaryKey: true}, |
| 130 | + &sql.Column{Name: "col1", Source: "test", Type: types.Text, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `""`, types.Text, false), PrimaryKey: false}, |
| 131 | + }) |
| 132 | + |
| 133 | + testTable := memory.NewTable(db.BaseDatabase, "test", schema, nil) |
| 134 | + db.AddTable("test", testTable) |
| 135 | + |
| 136 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(1), "brave"})) |
| 137 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(2), textErrorWrapper})) |
| 138 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(3), "!"})) |
| 139 | + |
| 140 | + TestQueryWithContext(t, ctx, e, harness, "ALTER TABLE test MODIFY COLUMN col1 LONGTEXT;", nil, nil, nil, nil) |
| 141 | +} |
| 142 | + |
| 143 | +// TestWrapperCopyWhenWideningColumn tests that widening a column doesn't cause values to be unwrapped. |
| 144 | +func TestWrapperCopyWhenNarrowingColumn(t *testing.T) { |
| 145 | + ctx, db, harness, e := setupWrapperTests(t) |
| 146 | + |
| 147 | + schema := sql.NewPrimaryKeySchema(sql.Schema{ |
| 148 | + &sql.Column{Name: "pk", Source: "test", Type: types.Int64, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `1`, types.Int64, false), PrimaryKey: true}, |
| 149 | + &sql.Column{Name: "col1", Source: "test", Type: types.LongText, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `""`, types.Text, false), PrimaryKey: false}, |
| 150 | + }) |
| 151 | + |
| 152 | + testTable := memory.NewTable(db.BaseDatabase, "test", schema, nil) |
| 153 | + db.AddTable("test", testTable) |
| 154 | + |
| 155 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(1), "brave"})) |
| 156 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(2), longTextErrorWrapper})) |
| 157 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(3), "!"})) |
| 158 | + |
| 159 | + AssertErrWithCtx(t, e, harness, ctx, "ALTER TABLE test MODIFY COLUMN col1 TEXT;", nil, nil, "unwrap failed") |
| 160 | +} |
| 161 | + |
| 162 | +func TestWrapperCopyWithExactLengthWhenNarrowingColumn(t *testing.T) { |
| 163 | + ctx, db, harness, e := setupWrapperTests(t) |
| 164 | + |
| 165 | + schema := sql.NewPrimaryKeySchema(sql.Schema{ |
| 166 | + &sql.Column{Name: "pk", Source: "test", Type: types.Int64, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `1`, types.Int64, false), PrimaryKey: true}, |
| 167 | + &sql.Column{Name: "col1", Source: "test", Type: types.LongText, Nullable: false, Default: planbuilder.MustStringToColumnDefaultValue(sql.NewEmptyContext(), `""`, types.Text, false), PrimaryKey: false}, |
| 168 | + }) |
| 169 | + |
| 170 | + testTable := memory.NewTable(db.BaseDatabase, "test", schema, nil) |
| 171 | + db.AddTable("test", testTable) |
| 172 | + |
| 173 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(1), "brave"})) |
| 174 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(2), exactLengthErrorWrapper(64)})) |
| 175 | + require.NoError(t, testTable.Insert(ctx, sql.Row{int64(3), "!"})) |
| 176 | + |
| 177 | + TestQueryWithContext(t, ctx, e, harness, "ALTER TABLE test MODIFY COLUMN col1 TEXT;", nil, nil, nil, nil) |
| 178 | +} |
0 commit comments