github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/sqlreplace_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 sqle
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	"github.com/dolthub/go-mysql-server/sql"
    22  	"github.com/google/uuid"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dtables"
    29  	"github.com/dolthub/dolt/go/store/types"
    30  )
    31  
    32  // Set to the name of a single test to run just that test, useful for debugging
    33  const singleReplaceQueryTest = "" //"Natural join with join clause"
    34  
    35  // Structure for a test of a replace query
    36  type ReplaceTest struct {
    37  	// The name of this test. Names should be unique and descriptive.
    38  	Name string
    39  	// The replace query to run
    40  	ReplaceQuery string
    41  	// The select query to run to verify the results
    42  	SelectQuery string
    43  	// The schema of the result of the query, nil if an error is expected
    44  	ExpectedSchema schema.Schema
    45  	// The rows this query should return, nil if an error is expected
    46  	ExpectedRows []sql.Row
    47  	// An expected error string
    48  	ExpectedErr string
    49  	// Setup logic to run before executing this test, after initial tables have been created and populated
    50  	AdditionalSetup SetupFn
    51  }
    52  
    53  // BasicReplaceTests cover basic replace statement features and error handling
    54  var BasicReplaceTests = []ReplaceTest{
    55  	{
    56  		Name:           "replace no columns",
    57  		ReplaceQuery:   "replace into people values (2, 'Bart', 'Simpson', false, 10, 9, '00000000-0000-0000-0000-000000000002', 222)",
    58  		SelectQuery:    "select * from people where id = 2 ORDER BY id",
    59  		ExpectedRows:   ToSqlRows(PeopleTestSchema, Bart),
    60  		ExpectedSchema: CompressSchema(PeopleTestSchema),
    61  	},
    62  	{
    63  		Name: "replace set",
    64  		ReplaceQuery: "replace into people set id = 2, first_name = 'Bart', last_name = 'Simpson'," +
    65  			"is_married = false, age = 10, rating = 9, uuid = '00000000-0000-0000-0000-000000000002', num_episodes = 222",
    66  		SelectQuery:    "select * from people where id = 2 ORDER BY id",
    67  		ExpectedRows:   ToSqlRows(PeopleTestSchema, Bart),
    68  		ExpectedSchema: CompressSchema(PeopleTestSchema),
    69  	},
    70  	{
    71  		Name:         "replace no columns too few values",
    72  		ReplaceQuery: "replace into people values (2, 'Bart', 'Simpson', false, 10, 9, '00000000-0000-0000-0000-000000000002')",
    73  		ExpectedErr:  "too few values",
    74  	},
    75  	{
    76  		Name:         "replace no columns too many values",
    77  		ReplaceQuery: "replace into people values (2, 'Bart', 'Simpson', false, 10, 9, '00000000-0000-0000-0000-000000000002', 222, 'abc')",
    78  		ExpectedErr:  "too many values",
    79  	},
    80  	{
    81  		Name:           "replace full columns",
    82  		ReplaceQuery:   "replace into people (id, first_name, last_name, is_married, age, rating, uuid, num_episodes) values (2, 'Bart', 'Simpson', false, 10, 9, '00000000-0000-0000-0000-000000000002', 222)",
    83  		SelectQuery:    "select * from people where id = 2 ORDER BY id",
    84  		ExpectedRows:   ToSqlRows(PeopleTestSchema, Bart),
    85  		ExpectedSchema: CompressSchema(PeopleTestSchema),
    86  	},
    87  	{
    88  		Name:           "replace full columns mixed order",
    89  		ReplaceQuery:   "replace into people (num_episodes, uuid, rating, age, is_married, last_name, first_name, id) values (222, '00000000-0000-0000-0000-000000000002', 9, 10, false, 'Simpson', 'Bart', 2)",
    90  		SelectQuery:    "select * from people where id = 2 ORDER BY id",
    91  		ExpectedRows:   ToSqlRows(PeopleTestSchema, Bart),
    92  		ExpectedSchema: CompressSchema(PeopleTestSchema),
    93  	},
    94  	{
    95  		Name: "replace full columns negative values",
    96  		ReplaceQuery: `replace into people (id, first_name, last_name, is_married, age, rating, uuid, num_episodes) values
    97  					    (-7, "Maggie", "Simpson", false, -1, -5.1, '00000000-0000-0000-0000-000000000005', 677)`,
    98  		SelectQuery:    "select * from people where id = -7 ORDER BY id",
    99  		ExpectedRows:   ToSqlRows(PeopleTestSchema, NewPeopleRowWithOptionalFields(-7, "Maggie", "Simpson", false, -1, -5.1, uuid.MustParse("00000000-0000-0000-0000-000000000005"), 677)),
   100  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   101  	},
   102  	{
   103  		Name:           "replace full columns null values",
   104  		ReplaceQuery:   "replace into people (id, first_name, last_name, is_married, age, rating, uuid, num_episodes) values (2, 'Bart', 'Simpson', null, null, null, null, null)",
   105  		SelectQuery:    "select * from people where id = 2 ORDER BY id",
   106  		ExpectedRows:   ToSqlRows(CompressSchema(PeopleTestSchema), NewResultSetRow(types.Int(2), types.String("Bart"), types.String("Simpson"))),
   107  		ExpectedSchema: CompressSchema(PeopleTestSchema),
   108  	},
   109  	{
   110  		Name:         "replace partial columns",
   111  		ReplaceQuery: "replace into people (id, first_name, last_name) values (2, 'Bart', 'Simpson')",
   112  		SelectQuery:  "select id, first_name, last_name from people where id = 2 ORDER BY id",
   113  		ExpectedRows: ToSqlRows(
   114  			NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   115  			NewResultSetRow(types.Int(2), types.String("Bart"), types.String("Simpson")),
   116  		),
   117  		ExpectedSchema: NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   118  	},
   119  	{
   120  		Name:         "replace partial columns mixed order",
   121  		ReplaceQuery: "replace into people (last_name, first_name, id) values ('Simpson', 'Bart', 2)",
   122  		SelectQuery:  "select id, first_name, last_name from people where id = 2 ORDER BY id",
   123  		ExpectedRows: ToSqlRows(
   124  			NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   125  			NewResultSetRow(types.Int(2), types.String("Bart"), types.String("Simpson")),
   126  		),
   127  		ExpectedSchema: NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   128  	},
   129  	{
   130  		Name:         "replace partial columns duplicate column",
   131  		ReplaceQuery: "replace into people (id, first_name, last_name, first_name) values (2, 'Bart', 'Simpson', 'Bart')",
   132  		ExpectedErr:  "duplicate column",
   133  	},
   134  	{
   135  		Name:         "replace partial columns invalid column",
   136  		ReplaceQuery: "replace into people (id, first_name, last_name, middle) values (2, 'Bart', 'Simpson', 'Nani')",
   137  		ExpectedErr:  "duplicate column",
   138  	},
   139  	{
   140  		Name:         "replace missing non-nullable column",
   141  		ReplaceQuery: "replace into people (id, first_name) values (2, 'Bart')",
   142  		ExpectedErr:  "column <last_name> received nil but is non-nullable",
   143  	},
   144  	{
   145  		Name:         "replace partial columns mismatch too many values",
   146  		ReplaceQuery: "replace into people (id, first_name, last_name) values (2, 'Bart', 'Simpson', false)",
   147  		ExpectedErr:  "too many values",
   148  	},
   149  	{
   150  		Name:         "replace partial columns mismatch too few values",
   151  		ReplaceQuery: "replace into people (id, first_name, last_name) values (2, 'Bart')",
   152  		ExpectedErr:  "too few values",
   153  	},
   154  	{
   155  		Name:         "replace partial columns functions",
   156  		ReplaceQuery: "replace into people (id, first_name, last_name) values (2, UPPER('Bart'), 'Simpson')",
   157  		SelectQuery:  "select id, first_name, last_name from people where id = 2 ORDER BY id",
   158  		ExpectedRows: ToSqlRows(
   159  			NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   160  			NewResultSetRow(types.Int(2), types.String("BART"), types.String("Simpson")),
   161  		),
   162  		ExpectedSchema: NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   163  	},
   164  	{
   165  		Name:         "replace partial columns multiple rows 2",
   166  		ReplaceQuery: "replace into people (id, first_name, last_name) values (0, 'Bart', 'Simpson'), (1, 'Homer', 'Simpson')",
   167  		SelectQuery:  "select id, first_name, last_name from people where id < 2 order by id",
   168  		ExpectedRows: ToSqlRows(
   169  			NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   170  			NewResultSetRow(types.Int(0), types.String("Bart"), types.String("Simpson")),
   171  			NewResultSetRow(types.Int(1), types.String("Homer"), types.String("Simpson")),
   172  		),
   173  		ExpectedSchema: NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   174  	},
   175  	{
   176  		Name: "replace partial columns multiple rows 5",
   177  		ReplaceQuery: `replace into people (id, first_name, last_name, is_married, age, rating) values
   178  					(7, "Maggie", "Simpson", false, 1, 5.1),
   179  					(8, "Milhouse", "Van Houten", false, 8, 3.5),
   180  					(9, "Jacqueline", "Bouvier", true, 80, 2),
   181  					(10, "Patty", "Bouvier", false, 40, 7),
   182  					(11, "Selma", "Bouvier", false, 40, 7)`,
   183  		SelectQuery: "select id, first_name, last_name, is_married, age, rating from people where id > 6 ORDER BY id",
   184  		ExpectedRows: ToSqlRows(SubsetSchema(PeopleTestSchema, "id", "first_name", "last_name", "is_married", "age", "rating"),
   185  			NewPeopleRow(7, "Maggie", "Simpson", false, 1, 5.1),
   186  			NewPeopleRow(8, "Milhouse", "Van Houten", false, 8, 3.5),
   187  			NewPeopleRow(9, "Jacqueline", "Bouvier", true, 80, 2),
   188  			NewPeopleRow(10, "Patty", "Bouvier", false, 40, 7),
   189  			NewPeopleRow(11, "Selma", "Bouvier", false, 40, 7),
   190  		),
   191  		ExpectedSchema: NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind,
   192  			"is_married", types.IntKind, "age", types.IntKind, "rating", types.FloatKind),
   193  	},
   194  	{
   195  		Name:         "replace partial columns multiple rows null pk",
   196  		ReplaceQuery: "replace into people (id, first_name, last_name) values (0, 'Bart', 'Simpson'), (1, 'Homer', null)",
   197  		ExpectedErr:  "column <last_name> received nil but is non-nullable",
   198  	},
   199  	{
   200  		Name:         "replace partial columns multiple rows duplicate",
   201  		ReplaceQuery: "replace into people (id, first_name, last_name) values (2, 'Bart', 'Simpson'), (2, 'Bart', 'Simpson')",
   202  		SelectQuery:  "select id, first_name, last_name from people where id = 2 ORDER BY id",
   203  		ExpectedRows: ToSqlRows(
   204  			NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   205  			NewResultSetRow(types.Int(2), types.String("Bart"), types.String("Simpson")),
   206  		),
   207  		ExpectedSchema: NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind),
   208  	},
   209  	{
   210  		Name: "replace partial columns multiple rows replace existing pk",
   211  		ReplaceQuery: `replace into people (id, first_name, last_name, is_married, age, rating) values
   212  					(0, "Homer", "Simpson", true, 45, 100),
   213  					(8, "Milhouse", "Van Houten", false, 8, 100)`,
   214  		SelectQuery: "select id, first_name, last_name, is_married, age, rating from people where rating = 100 order by id",
   215  		ExpectedRows: ToSqlRows(SubsetSchema(PeopleTestSchema, "id", "first_name", "last_name", "is_married", "age", "rating"),
   216  			NewPeopleRow(0, "Homer", "Simpson", true, 45, 100),
   217  			NewPeopleRow(8, "Milhouse", "Van Houten", false, 8, 100),
   218  		),
   219  		ExpectedSchema: NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind,
   220  			"is_married", types.IntKind, "age", types.IntKind, "rating", types.FloatKind),
   221  	},
   222  	{
   223  		Name: "replace partial columns multiple rows null pk",
   224  		ReplaceQuery: `replace into people (id, first_name, last_name, is_married, age, rating) values
   225  					(0, "Homer", "Simpson", true, 45, 100),
   226  					(8, "Milhouse", "Van Houten", false, 8, 3.5),
   227  					(7, "Maggie", null, false, 1, 5.1)`,
   228  		ExpectedErr: "Constraint failed for column 'last_name': Not null",
   229  	},
   230  	{
   231  		Name: "replace partial columns existing pk",
   232  		AdditionalSetup: ExecuteSetupSQL(context.Background(), `
   233  			CREATE TABLE temppeople (id bigint primary key, first_name varchar(1023), last_name varchar(1023), num bigint);
   234  			INSERT INTO temppeople VALUES (2, 'Bart', 'Simpson', 44);`),
   235  		ReplaceQuery: "replace into temppeople (id, first_name, last_name, num) values (2, 'Bart', 'Simpson', 88)",
   236  		SelectQuery:  "select id, first_name, last_name, num from temppeople where id = 2 ORDER BY id",
   237  		ExpectedRows: ToSqlRows(
   238  			NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind, "num", types.IntKind),
   239  			NewResultSetRow(types.Int(2), types.String("Bart"), types.String("Simpson"), types.Int(88))),
   240  		ExpectedSchema: NewResultSetSchema("id", types.IntKind, "first_name", types.StringKind, "last_name", types.StringKind, "num", types.IntKind),
   241  	},
   242  }
   243  
   244  func TestExecuteReplace(t *testing.T) {
   245  	for _, test := range BasicReplaceTests {
   246  		t.Run(test.Name, func(t *testing.T) {
   247  			testReplaceQuery(t, test)
   248  		})
   249  	}
   250  }
   251  
   252  var systemTableReplaceTests = []ReplaceTest{
   253  	{
   254  		Name: "replace into dolt_docs",
   255  		AdditionalSetup: CreateTableFn("dolt_docs", doltdb.DocsSchema,
   256  			"INSERT INTO dolt_docs VALUES ('LICENSE.md','A license')"),
   257  		ReplaceQuery:   "replace into dolt_docs (doc_name, doc_text) values ('LICENSE.md', 'Some text')",
   258  		SelectQuery:    "select * from dolt_docs",
   259  		ExpectedRows:   []sql.Row{{"LICENSE.md", "Some text"}},
   260  		ExpectedSchema: CompressSchema(doltdb.DocsSchema),
   261  	},
   262  	{
   263  		Name: "replace into dolt_query_catalog",
   264  		AdditionalSetup: CreateTableFn(doltdb.DoltQueryCatalogTableName, dtables.DoltQueryCatalogSchema,
   265  			"INSERT INTO dolt_query_catalog VALUES ('existingEntry', 1, 'example', 'select 2+2 from dual', 'description')"),
   266  		ReplaceQuery: "replace into dolt_query_catalog (id, display_order, name, query, description) values ('existingEntry', 1, 'example', 'select 1+1 from dual', 'description')",
   267  		SelectQuery:  "select * from dolt_query_catalog",
   268  		ExpectedRows: ToSqlRows(CompressSchema(dtables.DoltQueryCatalogSchema),
   269  			NewRow(types.String("existingEntry"), types.Uint(1), types.String("example"), types.String("select 1+1 from dual"), types.String("description")),
   270  		),
   271  		ExpectedSchema: CompressSchema(dtables.DoltQueryCatalogSchema),
   272  	},
   273  	{
   274  		Name: "replace into dolt_schemas",
   275  		AdditionalSetup: CreateTableFn(doltdb.SchemasTableName, schemaTableSchema,
   276  			"INSERT INTO dolt_schemas VALUES ('view', 'name', 'create view name as select 2+2 from dual', NULL, NULL)"),
   277  		ReplaceQuery:   "replace into dolt_schemas (type, name, fragment) values ('view', 'name', 'create view name as select 1+1 from dual')",
   278  		SelectQuery:    "select type, name, fragment, extra, sql_mode from dolt_schemas",
   279  		ExpectedRows:   []sql.Row{{"view", "name", "create view name as select 1+1 from dual", nil, nil}},
   280  		ExpectedSchema: CompressSchema(schemaTableSchema),
   281  	},
   282  }
   283  
   284  func TestReplaceIntoSystemTables(t *testing.T) {
   285  	for _, test := range systemTableReplaceTests {
   286  		t.Run(test.Name, func(t *testing.T) {
   287  			testReplaceQuery(t, test)
   288  		})
   289  	}
   290  }
   291  
   292  // Tests the given query on a freshly created dataset, asserting that the result has the given schema and rows. If
   293  // expectedErr is set, asserts instead that the execution returns an error that matches.
   294  func testReplaceQuery(t *testing.T, test ReplaceTest) {
   295  	if (test.ExpectedRows == nil) != (test.ExpectedSchema == nil) {
   296  		require.Fail(t, "Incorrect test setup: schema and rows must both be provided if one is")
   297  	}
   298  
   299  	if len(singleReplaceQueryTest) > 0 && test.Name != singleReplaceQueryTest {
   300  		t.Skip("Skipping tests until " + singleReplaceQueryTest)
   301  	}
   302  
   303  	dEnv, err := CreateEmptyTestDatabase()
   304  	require.NoError(t, err)
   305  	defer dEnv.DoltDB.Close()
   306  
   307  	if test.AdditionalSetup != nil {
   308  		test.AdditionalSetup(t, dEnv)
   309  	}
   310  
   311  	root, _ := dEnv.WorkingRoot(context.Background())
   312  	root, err = executeModify(t, context.Background(), dEnv, root, test.ReplaceQuery)
   313  	if len(test.ExpectedErr) > 0 {
   314  		require.Error(t, err)
   315  		return
   316  	} else {
   317  		require.NoError(t, err)
   318  	}
   319  
   320  	actualRows, sch, err := executeSelect(t, context.Background(), dEnv, root, test.SelectQuery)
   321  	require.NoError(t, err)
   322  
   323  	assert.Equal(t, test.ExpectedRows, actualRows)
   324  	assertSchemasEqual(t, mustSqlSchema(test.ExpectedSchema), sch)
   325  }