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 }