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 }