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