github.com/dolthub/go-mysql-server@v0.18.0/sql/rowexec/alter_table_test.go (about)

     1  // Copyright 2022 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 rowexec
    16  
    17  import (
    18  	"testing"
    19  
    20  	"github.com/dolthub/vitess/go/sqltypes"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  
    24  	"github.com/dolthub/go-mysql-server/sql"
    25  	"github.com/dolthub/go-mysql-server/sql/expression"
    26  	"github.com/dolthub/go-mysql-server/sql/plan"
    27  	"github.com/dolthub/go-mysql-server/sql/types"
    28  )
    29  
    30  func TestAddColumnToSchema(t *testing.T) {
    31  	myTable := sql.Schema{
    32  		{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
    33  		{Name: "s", Type: types.MustCreateStringWithDefaults(sqltypes.VarChar, 20), Source: "mytable", Comment: "column s"},
    34  	}
    35  
    36  	type testCase struct {
    37  		name        string
    38  		schema      sql.Schema
    39  		newColumn   *sql.Column
    40  		order       *sql.ColumnOrder
    41  		newSchema   sql.Schema
    42  		projections []sql.Expression
    43  	}
    44  
    45  	varchar20 := types.MustCreateStringWithDefaults(sqltypes.VarChar, 20)
    46  	testCases := []testCase{
    47  		{
    48  			name:      "add at end",
    49  			schema:    myTable,
    50  			newColumn: &sql.Column{Name: "i2", Type: types.Int64, Source: "mytable"},
    51  			newSchema: sql.Schema{
    52  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
    53  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
    54  				{Name: "i2", Type: types.Int64, Source: "mytable"},
    55  			},
    56  			projections: []sql.Expression{
    57  				expression.NewGetField(0, types.Int64, "i", false),
    58  				expression.NewGetField(1, varchar20, "s", false),
    59  				plan.ColDefaultExpression{&sql.Column{Name: "i2", Type: types.Int64, Source: "mytable"}},
    60  			},
    61  		},
    62  		{
    63  			name:      "add at end, with 'after'",
    64  			schema:    myTable,
    65  			newColumn: &sql.Column{Name: "i2", Type: types.Int64, Source: "mytable"},
    66  			order:     &sql.ColumnOrder{AfterColumn: "s"},
    67  			newSchema: sql.Schema{
    68  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
    69  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
    70  				{Name: "i2", Type: types.Int64, Source: "mytable"},
    71  			},
    72  			projections: []sql.Expression{
    73  				expression.NewGetField(0, types.Int64, "i", false),
    74  				expression.NewGetField(1, varchar20, "s", false),
    75  				plan.ColDefaultExpression{&sql.Column{Name: "i2", Type: types.Int64, Source: "mytable"}},
    76  			},
    77  		},
    78  		{
    79  			name:      "add at beginning",
    80  			schema:    myTable,
    81  			newColumn: &sql.Column{Name: "i2", Type: types.Int64, Source: "mytable"},
    82  			order:     &sql.ColumnOrder{First: true},
    83  			newSchema: sql.Schema{
    84  				{Name: "i2", Type: types.Int64, Source: "mytable"},
    85  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
    86  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
    87  			},
    88  			projections: []sql.Expression{
    89  				plan.ColDefaultExpression{&sql.Column{Name: "i2", Type: types.Int64, Source: "mytable"}},
    90  				expression.NewGetField(0, types.Int64, "i", false),
    91  				expression.NewGetField(1, varchar20, "s", false),
    92  			},
    93  		},
    94  		{
    95  			name:   "add at beginning with default",
    96  			schema: myTable,
    97  			newColumn: &sql.Column{
    98  				Name:    "i2",
    99  				Type:    types.Int64,
   100  				Source:  "mytable",
   101  				Default: mustDefault(expression.NewGetField(1, types.Int64, "i", false), types.Int64, false, true, true),
   102  			},
   103  			order: &sql.ColumnOrder{First: true},
   104  			newSchema: sql.Schema{
   105  				{Name: "i2", Type: types.Int64, Source: "mytable", Default: mustDefault(expression.NewGetField(0, types.Int64, "i", false), types.Int64, false, true, true)},
   106  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   107  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
   108  			},
   109  			projections: []sql.Expression{
   110  				plan.ColDefaultExpression{&sql.Column{
   111  					Name:    "i2",
   112  					Type:    types.Int64,
   113  					Source:  "mytable",
   114  					Default: mustDefault(expression.NewGetField(0, types.Int64, "i", false), types.Int64, false, true, true),
   115  				}},
   116  				expression.NewGetField(0, types.Int64, "i", false),
   117  				expression.NewGetField(1, varchar20, "s", false),
   118  			},
   119  		},
   120  		{
   121  			name:      "add in middle",
   122  			schema:    myTable,
   123  			newColumn: &sql.Column{Name: "i2", Type: types.Int64, Source: "mytable"},
   124  			order:     &sql.ColumnOrder{AfterColumn: "i"},
   125  			newSchema: sql.Schema{
   126  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   127  				{Name: "i2", Type: types.Int64, Source: "mytable"},
   128  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
   129  			},
   130  			projections: []sql.Expression{
   131  				expression.NewGetField(0, types.Int64, "i", false),
   132  				plan.ColDefaultExpression{&sql.Column{Name: "i2", Type: types.Int64, Source: "mytable"}},
   133  				expression.NewGetField(1, varchar20, "s", false),
   134  			},
   135  		},
   136  		{
   137  			name:   "add in middle with default",
   138  			schema: myTable,
   139  			newColumn: &sql.Column{
   140  				Name:    "i2",
   141  				Type:    types.Int64,
   142  				Source:  "mytable",
   143  				Default: mustDefault(expression.NewGetField(2, types.Int64, "s", false), types.Int64, false, true, true),
   144  			},
   145  			order: &sql.ColumnOrder{AfterColumn: "i"},
   146  			newSchema: sql.Schema{
   147  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   148  				{Name: "i2", Type: types.Int64, Source: "mytable", Default: mustDefault(expression.NewGetField(1, types.Int64, "s", false), types.Int64, false, true, true)},
   149  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
   150  			},
   151  			projections: []sql.Expression{
   152  				expression.NewGetField(0, types.Int64, "i", false),
   153  				plan.ColDefaultExpression{&sql.Column{
   154  					Name:    "i2",
   155  					Type:    types.Int64,
   156  					Source:  "mytable",
   157  					Default: mustDefault(expression.NewGetField(1, types.Int64, "s", false), types.Int64, false, true, true),
   158  				}},
   159  				expression.NewGetField(1, varchar20, "s", false),
   160  			},
   161  		},
   162  	}
   163  
   164  	for _, tc := range testCases {
   165  		t.Run(tc.name, func(t *testing.T) {
   166  			schema, projections, err := addColumnToSchema(tc.schema, tc.newColumn, tc.order)
   167  			if err != nil {
   168  				return
   169  			}
   170  			require.NoError(t, err)
   171  			assert.Equal(t, tc.newSchema, schema)
   172  			assert.Equal(t, tc.projections, projections)
   173  		})
   174  	}
   175  }
   176  
   177  func TestModifyColumnInSchema(t *testing.T) {
   178  	varchar20 := types.MustCreateStringWithDefaults(sqltypes.VarChar, 20)
   179  
   180  	myTable := sql.Schema{
   181  		{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   182  		{Name: "f", Type: types.Float64, Source: "mytable"},
   183  		{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
   184  	}
   185  
   186  	type testCase struct {
   187  		name        string
   188  		schema      sql.Schema
   189  		colName     string
   190  		newColumn   *sql.Column
   191  		order       *sql.ColumnOrder
   192  		newSchema   sql.Schema
   193  		projections []sql.Expression
   194  	}
   195  
   196  	testCases := []testCase{
   197  		{
   198  			name:      "modify last in place",
   199  			schema:    myTable,
   200  			colName:   "s",
   201  			newColumn: &sql.Column{Name: "s2", Type: types.Int64, Source: "mytable"},
   202  			newSchema: sql.Schema{
   203  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   204  				{Name: "f", Type: types.Float64, Source: "mytable"},
   205  				{Name: "s2", Type: types.Int64, Source: "mytable"},
   206  			},
   207  			projections: []sql.Expression{
   208  				expression.NewGetField(0, types.Int64, "i", false),
   209  				expression.NewGetField(1, types.Float64, "f", false),
   210  				expression.NewGetField(2, varchar20, "s", false),
   211  			},
   212  		},
   213  		{
   214  			name:      "modify first in place",
   215  			schema:    myTable,
   216  			colName:   "i",
   217  			newColumn: &sql.Column{Name: "i2", Type: types.Int64, Source: "mytable", Comment: "my comment", PrimaryKey: true},
   218  			newSchema: sql.Schema{
   219  				{Name: "i2", Type: types.Int64, Source: "mytable", Comment: "my comment", PrimaryKey: true},
   220  				{Name: "f", Type: types.Float64, Source: "mytable"},
   221  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
   222  			},
   223  			projections: []sql.Expression{
   224  				expression.NewGetField(0, types.Int64, "i", false),
   225  				expression.NewGetField(1, types.Float64, "f", false),
   226  				expression.NewGetField(2, varchar20, "s", false),
   227  			},
   228  		},
   229  		{
   230  			name:      "modify first, move to middle",
   231  			schema:    myTable,
   232  			colName:   "i",
   233  			order:     &sql.ColumnOrder{AfterColumn: "F"},
   234  			newColumn: &sql.Column{Name: "i2", Type: types.Int64, Source: "mytable", Comment: "my comment", PrimaryKey: true},
   235  			newSchema: sql.Schema{
   236  				{Name: "f", Type: types.Float64, Source: "mytable"},
   237  				{Name: "i2", Type: types.Int64, Source: "mytable", Comment: "my comment", PrimaryKey: true},
   238  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
   239  			},
   240  			projections: []sql.Expression{
   241  				expression.NewGetField(1, types.Float64, "f", false),
   242  				expression.NewGetField(0, types.Int64, "i", false),
   243  				expression.NewGetField(2, varchar20, "s", false),
   244  			},
   245  		},
   246  		{
   247  			name:      "modify first, move to end",
   248  			schema:    myTable,
   249  			colName:   "i",
   250  			order:     &sql.ColumnOrder{AfterColumn: "s"},
   251  			newColumn: &sql.Column{Name: "i2", Type: types.Int64, Source: "mytable", Comment: "my comment", PrimaryKey: true},
   252  			newSchema: sql.Schema{
   253  				{Name: "f", Type: types.Float64, Source: "mytable"},
   254  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
   255  				{Name: "i2", Type: types.Int64, Source: "mytable", Comment: "my comment", PrimaryKey: true},
   256  			},
   257  			projections: []sql.Expression{
   258  				expression.NewGetField(1, types.Float64, "f", false),
   259  				expression.NewGetField(2, varchar20, "s", false),
   260  				expression.NewGetField(0, types.Int64, "i", false),
   261  			},
   262  		},
   263  		{
   264  			name:      "modify last, move first",
   265  			schema:    myTable,
   266  			colName:   "s",
   267  			order:     &sql.ColumnOrder{First: true},
   268  			newColumn: &sql.Column{Name: "s2", Type: types.Int64, Source: "mytable", Comment: "my comment"},
   269  			newSchema: sql.Schema{
   270  				{Name: "s2", Type: types.Int64, Source: "mytable", Comment: "my comment"},
   271  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   272  				{Name: "f", Type: types.Float64, Source: "mytable"},
   273  			},
   274  			projections: []sql.Expression{
   275  				expression.NewGetField(2, varchar20, "s", false),
   276  				expression.NewGetField(0, types.Int64, "i", false),
   277  				expression.NewGetField(1, types.Float64, "f", false),
   278  			},
   279  		},
   280  		{
   281  			name:      "modify middle, move first",
   282  			schema:    myTable,
   283  			colName:   "f",
   284  			order:     &sql.ColumnOrder{First: true},
   285  			newColumn: &sql.Column{Name: "f2", Type: types.Int64, Source: "mytable", Comment: "my comment"},
   286  			newSchema: sql.Schema{
   287  				{Name: "f2", Type: types.Int64, Source: "mytable", Comment: "my comment"},
   288  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   289  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
   290  			},
   291  			projections: []sql.Expression{
   292  				expression.NewGetField(1, types.Float64, "f", false),
   293  				expression.NewGetField(0, types.Int64, "i", false),
   294  				expression.NewGetField(2, varchar20, "s", false),
   295  			},
   296  		},
   297  		{
   298  			name:      "modify middle, move to middle",
   299  			schema:    myTable,
   300  			colName:   "f",
   301  			order:     &sql.ColumnOrder{AfterColumn: "I"},
   302  			newColumn: &sql.Column{Name: "f2", Type: types.Int64, Source: "mytable", Comment: "my comment"},
   303  			newSchema: sql.Schema{
   304  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   305  				{Name: "f2", Type: types.Int64, Source: "mytable", Comment: "my comment"},
   306  				{Name: "s", Type: varchar20, Source: "mytable", Comment: "column s"},
   307  			},
   308  			projections: []sql.Expression{
   309  				expression.NewGetField(0, types.Int64, "i", false),
   310  				expression.NewGetField(1, types.Float64, "f", false),
   311  				expression.NewGetField(2, varchar20, "s", false),
   312  			},
   313  		},
   314  		{
   315  			name:      "modify last, move to middle",
   316  			schema:    myTable,
   317  			colName:   "s",
   318  			order:     &sql.ColumnOrder{AfterColumn: "I"},
   319  			newColumn: &sql.Column{Name: "s2", Type: types.Int64, Source: "mytable", Comment: "my comment"},
   320  			newSchema: sql.Schema{
   321  				{Name: "i", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   322  				{Name: "s2", Type: types.Int64, Source: "mytable", Comment: "my comment"},
   323  				{Name: "f", Type: types.Float64, Source: "mytable"},
   324  			},
   325  			projections: []sql.Expression{
   326  				expression.NewGetField(0, types.Int64, "i", false),
   327  				expression.NewGetField(2, varchar20, "s", false),
   328  				expression.NewGetField(1, types.Float64, "f", false),
   329  			},
   330  		},
   331  		{
   332  			name: "modify middle, move first with defaults",
   333  			schema: sql.Schema{
   334  				{Name: "one", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   335  				{Name: "two", Type: types.Int64, Source: "mytable"},
   336  				{Name: "three", Type: types.Int64, Source: "mytable", Default: mustDefault(
   337  					expression.NewGetFieldWithTable(1, 1, types.Int64, "", "mytable", "two", false),
   338  					types.Int64, false, true, false),
   339  				},
   340  			},
   341  			colName: "two",
   342  			order:   &sql.ColumnOrder{First: true},
   343  			newColumn: &sql.Column{Name: "two", Type: types.Int64, Source: "mytable", Default: mustDefault(
   344  				expression.NewGetFieldWithTable(0, 1, types.Int64, "", "mytable", "one", false),
   345  				types.Int64, false, true, false),
   346  			},
   347  			newSchema: sql.Schema{
   348  				{Name: "two", Type: types.Int64, Source: "mytable", Default: mustDefault(
   349  					expression.NewGetFieldWithTable(1, 1, types.Int64, "", "mytable", "one", false),
   350  					types.Int64, false, true, false),
   351  				},
   352  				{Name: "one", Type: types.Int64, Source: "mytable", PrimaryKey: true},
   353  				{Name: "three", Type: types.Int64, Source: "mytable", Default: mustDefault(
   354  					expression.NewGetFieldWithTable(0, 1, types.Int64, "", "mytable", "two", false),
   355  					types.Int64, false, true, false),
   356  				},
   357  			},
   358  			projections: []sql.Expression{
   359  				expression.NewGetFieldWithTable(1, 0, types.Int64, "", "", "two", false),
   360  				expression.NewGetFieldWithTable(0, 0, types.Int64, "", "", "one", false),
   361  				expression.NewGetFieldWithTable(2, 0, types.Int64, "", "", "three", false),
   362  			},
   363  		},
   364  	}
   365  
   366  	for _, tc := range testCases {
   367  		t.Run(tc.name, func(t *testing.T) {
   368  			schema, projections, err := modifyColumnInSchema(tc.schema, tc.colName, tc.newColumn, tc.order)
   369  			if err != nil {
   370  				return
   371  			}
   372  			require.NoError(t, err)
   373  			assert.Equal(t, tc.newSchema, schema)
   374  			assert.Equal(t, tc.projections, projections)
   375  		})
   376  	}
   377  }
   378  
   379  // mustDefault enforces that no error occurred when constructing the column default value.
   380  func mustDefault(expr sql.Expression, outType sql.Type, representsLiteral bool, parenthesized bool, mayReturnNil bool) *sql.ColumnDefaultValue {
   381  	colDef, err := sql.NewColumnDefaultValue(expr, outType, representsLiteral, parenthesized, mayReturnNil)
   382  	if err != nil {
   383  		panic(err)
   384  	}
   385  	return colDef
   386  }