github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/sqlfmt/row_fmt_test.go (about) 1 // Copyright 2020 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 sqlfmt_test 16 17 import ( 18 "testing" 19 20 "github.com/google/uuid" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 24 "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils" 25 "github.com/dolthub/dolt/go/libraries/doltcore/row" 26 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 27 "github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo" 28 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlfmt" 29 "github.com/dolthub/dolt/go/libraries/utils/set" 30 "github.com/dolthub/dolt/go/store/types" 31 ) 32 33 const expectedDropSql = "DROP TABLE `table_name`;" 34 const expectedDropIfExistsSql = "DROP TABLE IF EXISTS `table_name`;" 35 const expectedAddColSql = "ALTER TABLE `table_name` ADD `c0` BIGINT NOT NULL;" 36 const expectedDropColSql = "ALTER TABLE `table_name` DROP `first_name`;" 37 const expectedRenameColSql = "ALTER TABLE `table_name` RENAME COLUMN `id` TO `pk`;" 38 const expectedRenameTableSql = "RENAME TABLE `table_name` TO `new_table_name`;" 39 40 type test struct { 41 name string 42 row row.Row 43 sch schema.Schema 44 expectedOutput string 45 } 46 47 type updateTest struct { 48 name string 49 row row.Row 50 sch schema.Schema 51 expectedOutput string 52 collDiff *set.StrSet 53 } 54 55 func TestTableDropStmt(t *testing.T) { 56 stmt := sqlfmt.DropTableStmt("table_name") 57 58 assert.Equal(t, expectedDropSql, stmt) 59 } 60 61 func TestTableDropIfExistsStmt(t *testing.T) { 62 stmt := sqlfmt.DropTableIfExistsStmt("table_name") 63 64 assert.Equal(t, expectedDropIfExistsSql, stmt) 65 } 66 67 func TestAlterTableAddColStmt(t *testing.T) { 68 newColDef := "`c0` BIGINT NOT NULL" 69 stmt := sqlfmt.AlterTableAddColStmt("table_name", newColDef) 70 71 assert.Equal(t, expectedAddColSql, stmt) 72 } 73 74 func TestAlterTableDropColStmt(t *testing.T) { 75 stmt := sqlfmt.AlterTableDropColStmt("table_name", "first_name") 76 77 assert.Equal(t, expectedDropColSql, stmt) 78 } 79 80 func TestAlterTableRenameColStmt(t *testing.T) { 81 stmt := sqlfmt.AlterTableRenameColStmt("table_name", "id", "pk") 82 83 assert.Equal(t, expectedRenameColSql, stmt) 84 } 85 86 func TestRenameTableStmt(t *testing.T) { 87 stmt := sqlfmt.RenameTableStmt("table_name", "new_table_name") 88 89 assert.Equal(t, expectedRenameTableSql, stmt) 90 } 91 92 func newRow(sch schema.Schema, id uuid.UUID, name string, age uint, isMarried bool, title *string) row.Row { 93 var titleVal types.Value 94 if title != nil { 95 titleVal = types.String(*title) 96 } 97 98 married := types.Int(0) 99 if isMarried { 100 married = types.Int(1) 101 } 102 103 taggedVals := row.TaggedValues{ 104 dtestutils.IdTag: types.String(id.String()), 105 dtestutils.NameTag: types.String(name), 106 dtestutils.AgeTag: types.Uint(age), 107 dtestutils.IsMarriedTag: married, 108 dtestutils.TitleTag: titleVal, 109 } 110 111 r, err := row.New(types.Format_Default, sch, taggedVals) 112 113 if err != nil { 114 panic(err) 115 } 116 117 return r 118 } 119 120 func TestRowAsInsertStmt(t *testing.T) { 121 id := uuid.MustParse("00000000-0000-0000-0000-000000000000") 122 tableName := "people" 123 124 sch, err := dtestutils.Schema() 125 require.NoError(t, err) 126 127 tests := []test{ 128 { 129 name: "simple row", 130 row: newRow(sch, id, "some guy", 100, false, strPointer("normie")), 131 sch: sch, 132 expectedOutput: "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) VALUES ('00000000-0000-0000-0000-000000000000','some guy',100,0,'normie');", 133 }, 134 { 135 name: "embedded quotes", 136 row: newRow(sch, id, `It's "Mister Perfect" to you`, 100, false, strPointer("normie")), 137 sch: sch, 138 expectedOutput: "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) VALUES ('00000000-0000-0000-0000-000000000000','It\\'s \\\"Mister Perfect\\\" to you',100,0,'normie');", 139 }, 140 { 141 name: "null values", 142 row: newRow(sch, id, "some guy", 100, false, nil), 143 sch: sch, 144 expectedOutput: "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) VALUES ('00000000-0000-0000-0000-000000000000','some guy',100,0,NULL);", 145 }, 146 } 147 148 trickySch := dtestutils.CreateSchema( 149 schema.NewColumn("a name with spaces", 0, types.FloatKind, false), 150 schema.NewColumn("anotherColumn", 1, types.IntKind, true), 151 ) 152 153 tests = append(tests, test{ 154 name: "negative values and columns with spaces", 155 row: dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42)), 156 sch: trickySch, 157 expectedOutput: "INSERT INTO `people` (`a name with spaces`,`anotherColumn`) VALUES (-3.14,-42);", 158 }) 159 160 for _, tt := range tests { 161 t.Run(tt.name, func(t *testing.T) { 162 stmt, err := sqlfmt.RowAsInsertStmt(tt.row, tableName, tt.sch) 163 assert.NoError(t, err) 164 assert.Equal(t, tt.expectedOutput, stmt) 165 }) 166 } 167 } 168 169 func TestRowAsDeleteStmt(t *testing.T) { 170 tableName := "tricky" 171 trickySch := dtestutils.CreateSchema( 172 schema.NewColumn("anotherCol", 0, types.FloatKind, false), 173 schema.NewColumn("a name with spaces", 1, types.IntKind, true), 174 ) 175 176 tests := []test{ 177 { 178 name: "negative values and columns with spaces", 179 row: dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42)), 180 sch: trickySch, 181 expectedOutput: "DELETE FROM `tricky` WHERE (`a name with spaces`=-42);", 182 }, 183 } 184 185 for _, tt := range tests { 186 t.Run(tt.name, func(t *testing.T) { 187 stmt, err := sqlfmt.RowAsDeleteStmt(tt.row, tableName, tt.sch) 188 assert.NoError(t, err) 189 assert.Equal(t, tt.expectedOutput, stmt) 190 }) 191 } 192 } 193 194 func TestRowAsUpdateStmt(t *testing.T) { 195 id := uuid.MustParse("00000000-0000-0000-0000-000000000000") 196 tableName := "people" 197 198 sch, err := dtestutils.Schema() 199 require.NoError(t, err) 200 201 tests := []updateTest{ 202 { 203 name: "simple row", 204 row: newRow(sch, id, "some guy", 100, false, strPointer("normie")), 205 sch: sch, 206 expectedOutput: "UPDATE `people` SET `name`='some guy',`age`=100,`is_married`=0,`title`='normie' WHERE (`id`='00000000-0000-0000-0000-000000000000');", 207 collDiff: set.NewStrSet([]string{"name", "age", "is_married", "title"}), 208 }, 209 { 210 name: "embedded quotes", 211 row: newRow(sch, id, `It's "Mister Perfect" to you`, 100, false, strPointer("normie")), 212 sch: sch, 213 expectedOutput: "UPDATE `people` SET `name`='It\\'s \\\"Mister Perfect\\\" to you',`age`=100,`is_married`=0,`title`='normie' WHERE (`id`='00000000-0000-0000-0000-000000000000');", 214 collDiff: set.NewStrSet([]string{"name", "age", "is_married", "title"}), 215 }, 216 { 217 name: "null values", 218 row: newRow(sch, id, "some guy", 100, false, nil), 219 sch: sch, 220 expectedOutput: "UPDATE `people` SET `name`='some guy',`age`=100,`is_married`=0,`title`=NULL WHERE (`id`='00000000-0000-0000-0000-000000000000');", 221 collDiff: set.NewStrSet([]string{"name", "age", "is_married", "title"}), 222 }, 223 { 224 name: "partial update", 225 row: newRow(sch, id, "some guy", 100, false, nil), 226 sch: sch, 227 expectedOutput: "UPDATE `people` SET `name`='some guy' WHERE (`id`='00000000-0000-0000-0000-000000000000');", 228 collDiff: set.NewStrSet([]string{"name"}), 229 }, 230 } 231 232 trickySch := dtestutils.CreateSchema( 233 schema.NewColumn("a name with spaces", 0, types.FloatKind, false), 234 schema.NewColumn("anotherColumn", 1, types.IntKind, true), 235 ) 236 237 tests = append(tests, updateTest{ 238 name: "negative values and columns with spaces", 239 row: dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42)), 240 sch: trickySch, 241 expectedOutput: "UPDATE `people` SET `a name with spaces`=-3.14 WHERE (`anotherColumn`=-42);", 242 collDiff: set.NewStrSet([]string{"a name with spaces"}), 243 }) 244 245 for _, tt := range tests { 246 t.Run(tt.name, func(t *testing.T) { 247 stmt, err := sqlfmt.RowAsUpdateStmt(tt.row, tableName, tt.sch, tt.collDiff) 248 assert.NoError(t, err) 249 assert.Equal(t, tt.expectedOutput, stmt) 250 }) 251 } 252 } 253 254 func TestValueAsSqlString(t *testing.T) { 255 tu, _ := uuid.Parse("00000000-0000-0000-0000-000000000000") 256 257 tests := []struct { 258 name string 259 val types.Value 260 ti typeinfo.TypeInfo 261 exp string 262 }{ 263 { 264 name: "bool(true)", 265 val: types.Bool(true), 266 ti: typeinfo.BoolType, 267 exp: "TRUE", 268 }, 269 { 270 name: "bool(false)", 271 val: types.Bool(false), 272 ti: typeinfo.BoolType, 273 exp: "FALSE", 274 }, 275 { 276 name: "uuid", 277 val: types.UUID(tu), 278 ti: typeinfo.UuidType, 279 exp: "'00000000-0000-0000-0000-000000000000'", 280 }, 281 { 282 name: "string", 283 val: types.String("leviosa"), 284 ti: typeinfo.StringDefaultType, 285 exp: "'leviosa'", 286 }, 287 { 288 // borrowed from vitess 289 name: "escape string", 290 val: types.String("\x00'\"\b\n\r\t\x1A\\"), 291 ti: typeinfo.StringDefaultType, 292 exp: "'\\0\\'\\\"\\b\\n\\r\\t\\Z\\\\'", 293 }, 294 // using only string and int types as an example, but includes all types 295 { 296 name: "NULL value for typeinfo.string types", 297 val: nil, 298 ti: typeinfo.StringDefaultType, 299 exp: "NULL", 300 }, 301 { 302 name: "NULL value for typeinfo.int types", 303 val: nil, 304 ti: typeinfo.Int64Type, 305 exp: "NULL", 306 }, 307 } 308 309 for _, test := range tests { 310 t.Run(test.name, func(t *testing.T) { 311 act, err := sqlfmt.ValueAsSqlString(test.ti, test.val) 312 require.NoError(t, err) 313 assert.Equal(t, test.exp, act) 314 }) 315 } 316 } 317 318 func strPointer(s string) *string { 319 return &s 320 }