github.com/seashell-org/golang-migrate/v4@v4.15.3-0.20220722221203-6ab6c6c062d1/database/spanner/spansql/sql_test.go (about) 1 /* 2 Copyright 2019 Google LLC 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package spansql 18 19 import ( 20 "reflect" 21 "testing" 22 "time" 23 24 "cloud.google.com/go/civil" 25 ) 26 27 func boolAddr(b bool) *bool { 28 return &b 29 } 30 31 func TestSQL(t *testing.T) { 32 reparseDDL := func(s string) (interface{}, error) { 33 ddl, err := ParseDDLStmt(s) 34 if err != nil { 35 return nil, err 36 } 37 ddl.clearOffset() 38 return ddl, nil 39 } 40 reparseDML := func(s string) (interface{}, error) { 41 dml, err := ParseDMLStmt(s) 42 if err != nil { 43 return nil, err 44 } 45 return dml, nil 46 } 47 reparseQuery := func(s string) (interface{}, error) { 48 q, err := ParseQuery(s) 49 return q, err 50 } 51 reparseExpr := func(s string) (interface{}, error) { 52 e, pe := newParser("f-expr", s).parseExpr() 53 if pe != nil { 54 return nil, pe 55 } 56 return e, nil 57 } 58 59 latz, err := time.LoadLocation("America/Los_Angeles") 60 if err != nil { 61 t.Fatalf("Loading Los Angeles time zone info: %v", err) 62 } 63 64 line := func(n int) Position { return Position{Line: n} } 65 tests := []struct { 66 data interface{ SQL() string } 67 sql string 68 reparse func(string) (interface{}, error) 69 }{ 70 { 71 &CreateTable{ 72 Name: "Ta", 73 Columns: []ColumnDef{ 74 {Name: "Ca", Type: Type{Base: Bool}, NotNull: true, Position: line(2)}, 75 {Name: "Cb", Type: Type{Base: Int64}, Position: line(3)}, 76 {Name: "Cc", Type: Type{Base: Float64}, Position: line(4)}, 77 {Name: "Cd", Type: Type{Base: String, Len: 17}, Position: line(5)}, 78 {Name: "Ce", Type: Type{Base: String, Len: MaxLen}, Position: line(6)}, 79 {Name: "Cf", Type: Type{Base: Bytes, Len: 4711}, Position: line(7)}, 80 {Name: "Cg", Type: Type{Base: Bytes, Len: MaxLen}, Position: line(8)}, 81 {Name: "Ch", Type: Type{Base: Date}, Position: line(9)}, 82 {Name: "Ci", Type: Type{Base: Timestamp}, Options: ColumnOptions{AllowCommitTimestamp: boolAddr(true)}, Position: line(10)}, 83 {Name: "Cj", Type: Type{Array: true, Base: Int64}, Position: line(11)}, 84 {Name: "Ck", Type: Type{Array: true, Base: String, Len: MaxLen}, Position: line(12)}, 85 {Name: "Cl", Type: Type{Base: Timestamp}, Options: ColumnOptions{AllowCommitTimestamp: boolAddr(false)}, Position: line(13)}, 86 {Name: "Cm", Type: Type{Base: Int64}, Generated: Func{Name: "CHAR_LENGTH", Args: []Expr{ID("Ce")}}, Position: line(14)}, 87 {Name: "Cn", Type: Type{Base: JSON}, Position: line(15)}, 88 {Name: "Co", Type: Type{Base: Int64}, Default: IntegerLiteral(1), Position: line(16)}, 89 }, 90 PrimaryKey: []KeyPart{ 91 {Column: "Ca"}, 92 {Column: "Cb", Desc: true}, 93 }, 94 Position: line(1), 95 }, 96 `CREATE TABLE Ta ( 97 Ca BOOL NOT NULL, 98 Cb INT64, 99 Cc FLOAT64, 100 Cd STRING(17), 101 Ce STRING(MAX), 102 Cf BYTES(4711), 103 Cg BYTES(MAX), 104 Ch DATE, 105 Ci TIMESTAMP OPTIONS (allow_commit_timestamp = true), 106 Cj ARRAY<INT64>, 107 Ck ARRAY<STRING(MAX)>, 108 Cl TIMESTAMP OPTIONS (allow_commit_timestamp = null), 109 Cm INT64 AS (CHAR_LENGTH(Ce)) STORED, 110 Cn JSON, 111 Co INT64 DEFAULT (1), 112 ) PRIMARY KEY(Ca, Cb DESC)`, 113 reparseDDL, 114 }, 115 { 116 &CreateTable{ 117 Name: "Tsub", 118 Columns: []ColumnDef{ 119 {Name: "SomeId", Type: Type{Base: Int64}, NotNull: true, Position: line(2)}, 120 {Name: "OtherId", Type: Type{Base: Int64}, NotNull: true, Position: line(3)}, 121 // This column name uses a reserved keyword. 122 {Name: "Hash", Type: Type{Base: Bytes, Len: 32}, Position: line(4)}, 123 }, 124 PrimaryKey: []KeyPart{ 125 {Column: "SomeId"}, 126 {Column: "OtherId"}, 127 }, 128 Interleave: &Interleave{ 129 Parent: "Ta", 130 OnDelete: CascadeOnDelete, 131 }, 132 Position: line(1), 133 }, 134 `CREATE TABLE Tsub ( 135 SomeId INT64 NOT NULL, 136 OtherId INT64 NOT NULL, 137 ` + "`Hash`" + ` BYTES(32), 138 ) PRIMARY KEY(SomeId, OtherId), 139 INTERLEAVE IN PARENT Ta ON DELETE CASCADE`, 140 reparseDDL, 141 }, 142 { 143 &CreateTable{ 144 Name: "WithRowDeletionPolicy", 145 Columns: []ColumnDef{ 146 {Name: "Name", Type: Type{Base: String, Len: MaxLen}, NotNull: true, Position: line(2)}, 147 {Name: "DelTimestamp", Type: Type{Base: Timestamp}, NotNull: true, Position: line(3)}, 148 }, 149 PrimaryKey: []KeyPart{{Column: "Name"}}, 150 RowDeletionPolicy: &RowDeletionPolicy{ 151 Column: ID("DelTimestamp"), 152 NumDays: 30, 153 }, 154 Position: line(1), 155 }, 156 `CREATE TABLE WithRowDeletionPolicy ( 157 Name STRING(MAX) NOT NULL, 158 DelTimestamp TIMESTAMP NOT NULL, 159 ) PRIMARY KEY(Name), 160 ROW DELETION POLICY ( OLDER_THAN ( DelTimestamp, INTERVAL 30 DAY ))`, 161 reparseDDL, 162 }, 163 { 164 &DropTable{ 165 Name: "Ta", 166 Position: line(1), 167 }, 168 "DROP TABLE Ta", 169 reparseDDL, 170 }, 171 { 172 &CreateIndex{ 173 Name: "Ia", 174 Table: "Ta", 175 Columns: []KeyPart{ 176 {Column: "Ca"}, 177 {Column: "Cb", Desc: true}, 178 }, 179 Position: line(1), 180 }, 181 "CREATE INDEX Ia ON Ta(Ca, Cb DESC)", 182 reparseDDL, 183 }, 184 { 185 &DropIndex{ 186 Name: "Ia", 187 Position: line(1), 188 }, 189 "DROP INDEX Ia", 190 reparseDDL, 191 }, 192 { 193 &CreateView{ 194 Name: "SingersView", 195 OrReplace: true, 196 Query: Query{ 197 Select: Select{ 198 List: []Expr{ID("SingerId"), ID("FullName"), ID("Picture")}, 199 From: []SelectFrom{SelectFromTable{ 200 Table: "Singers", 201 }}, 202 }, 203 Order: []Order{ 204 {Expr: ID("LastName")}, 205 {Expr: ID("FirstName")}, 206 }, 207 }, 208 Position: line(1), 209 }, 210 "CREATE OR REPLACE VIEW SingersView SQL SECURITY INVOKER AS SELECT SingerId, FullName, Picture FROM Singers ORDER BY LastName, FirstName", 211 reparseDDL, 212 }, 213 { 214 &DropView{ 215 Name: "SingersView", 216 Position: line(1), 217 }, 218 "DROP VIEW SingersView", 219 reparseDDL, 220 }, 221 { 222 &AlterTable{ 223 Name: "Ta", 224 Alteration: AddColumn{Def: ColumnDef{Name: "Ca", Type: Type{Base: Bool}, Position: line(1)}}, 225 Position: line(1), 226 }, 227 "ALTER TABLE Ta ADD COLUMN Ca BOOL", 228 reparseDDL, 229 }, 230 { 231 &AlterTable{ 232 Name: "Ta", 233 Alteration: DropColumn{Name: "Ca"}, 234 Position: line(1), 235 }, 236 "ALTER TABLE Ta DROP COLUMN Ca", 237 reparseDDL, 238 }, 239 { 240 &AlterTable{ 241 Name: "Ta", 242 Alteration: SetOnDelete{Action: NoActionOnDelete}, 243 Position: line(1), 244 }, 245 "ALTER TABLE Ta SET ON DELETE NO ACTION", 246 reparseDDL, 247 }, 248 { 249 &AlterTable{ 250 Name: "Ta", 251 Alteration: SetOnDelete{Action: CascadeOnDelete}, 252 Position: line(1), 253 }, 254 "ALTER TABLE Ta SET ON DELETE CASCADE", 255 reparseDDL, 256 }, 257 { 258 &AlterTable{ 259 Name: "Ta", 260 Alteration: AlterColumn{ 261 Name: "Cg", 262 Alteration: SetColumnType{ 263 Type: Type{Base: String, Len: MaxLen}, 264 }, 265 }, 266 Position: line(1), 267 }, 268 "ALTER TABLE Ta ALTER COLUMN Cg STRING(MAX)", 269 reparseDDL, 270 }, 271 { 272 &AlterTable{ 273 Name: "Ta", 274 Alteration: AlterColumn{ 275 Name: "Ch", 276 Alteration: SetColumnType{ 277 Type: Type{Base: String, Len: MaxLen}, 278 NotNull: true, 279 Default: StringLiteral("1"), 280 }, 281 }, 282 Position: line(1), 283 }, 284 "ALTER TABLE Ta ALTER COLUMN Ch STRING(MAX) NOT NULL DEFAULT (\"1\")", 285 reparseDDL, 286 }, 287 { 288 &AlterTable{ 289 Name: "Ta", 290 Alteration: AlterColumn{ 291 Name: "Ci", 292 Alteration: SetColumnOptions{ 293 Options: ColumnOptions{ 294 AllowCommitTimestamp: boolAddr(false), 295 }, 296 }, 297 }, 298 Position: line(1), 299 }, 300 "ALTER TABLE Ta ALTER COLUMN Ci SET OPTIONS (allow_commit_timestamp = null)", 301 reparseDDL, 302 }, 303 { 304 &AlterTable{ 305 Name: "Ta", 306 Alteration: AlterColumn{ 307 Name: "Cj", 308 Alteration: SetDefault{ 309 Default: StringLiteral("1"), 310 }, 311 }, 312 Position: line(1), 313 }, 314 "ALTER TABLE Ta ALTER COLUMN Cj SET DEFAULT (\"1\")", 315 reparseDDL, 316 }, 317 { 318 &AlterTable{ 319 Name: "Ta", 320 Alteration: AlterColumn{ 321 Name: "Ck", 322 Alteration: DropDefault{}, 323 }, 324 Position: line(1), 325 }, 326 "ALTER TABLE Ta ALTER COLUMN Ck DROP DEFAULT", 327 reparseDDL, 328 }, 329 { 330 &AlterTable{ 331 Name: "WithRowDeletionPolicy", 332 Alteration: DropRowDeletionPolicy{}, 333 Position: line(1), 334 }, 335 "ALTER TABLE WithRowDeletionPolicy DROP ROW DELETION POLICY", 336 reparseDDL, 337 }, 338 { 339 &AlterTable{ 340 Name: "WithRowDeletionPolicy", 341 Alteration: AddRowDeletionPolicy{ 342 RowDeletionPolicy: RowDeletionPolicy{ 343 Column: ID("DelTimestamp"), 344 NumDays: 30, 345 }, 346 }, 347 Position: line(1), 348 }, 349 "ALTER TABLE WithRowDeletionPolicy ADD ROW DELETION POLICY ( OLDER_THAN ( DelTimestamp, INTERVAL 30 DAY ))", 350 reparseDDL, 351 }, 352 { 353 &AlterTable{ 354 Name: "WithRowDeletionPolicy", 355 Alteration: ReplaceRowDeletionPolicy{ 356 RowDeletionPolicy: RowDeletionPolicy{ 357 Column: ID("DelTimestamp"), 358 NumDays: 30, 359 }, 360 }, 361 Position: line(1), 362 }, 363 "ALTER TABLE WithRowDeletionPolicy REPLACE ROW DELETION POLICY ( OLDER_THAN ( DelTimestamp, INTERVAL 30 DAY ))", 364 reparseDDL, 365 }, 366 { 367 &AlterDatabase{ 368 Name: "dbname", 369 Alteration: SetDatabaseOptions{Options: DatabaseOptions{ 370 EnableKeyVisualizer: func(b bool) *bool { return &b }(true), 371 }}, 372 Position: line(1), 373 }, 374 "ALTER DATABASE dbname SET OPTIONS (enable_key_visualizer=true)", 375 reparseDDL, 376 }, 377 { 378 &AlterDatabase{ 379 Name: "dbname", 380 Alteration: SetDatabaseOptions{Options: DatabaseOptions{ 381 OptimizerVersion: func(i int) *int { return &i }(2), 382 }}, 383 Position: line(1), 384 }, 385 "ALTER DATABASE dbname SET OPTIONS (optimizer_version=2)", 386 reparseDDL, 387 }, 388 { 389 &AlterDatabase{ 390 Name: "dbname", 391 Alteration: SetDatabaseOptions{Options: DatabaseOptions{ 392 VersionRetentionPeriod: func(s string) *string { return &s }("7d"), 393 OptimizerVersion: func(i int) *int { return &i }(2), 394 EnableKeyVisualizer: func(b bool) *bool { return &b }(true), 395 }}, 396 Position: line(1), 397 }, 398 "ALTER DATABASE dbname SET OPTIONS (optimizer_version=2, version_retention_period='7d', enable_key_visualizer=true)", 399 reparseDDL, 400 }, 401 { 402 &AlterDatabase{ 403 Name: "dbname", 404 Alteration: SetDatabaseOptions{Options: DatabaseOptions{ 405 VersionRetentionPeriod: func(s string) *string { return &s }(""), 406 OptimizerVersion: func(i int) *int { return &i }(0), 407 EnableKeyVisualizer: func(b bool) *bool { return &b }(false), 408 }}, 409 Position: line(1), 410 }, 411 "ALTER DATABASE dbname SET OPTIONS (optimizer_version=null, version_retention_period=null, enable_key_visualizer=null)", 412 reparseDDL, 413 }, 414 { 415 &Insert{ 416 Table: "Singers", 417 Columns: []ID{ID("SingerId"), ID("FirstName"), ID("LastName")}, 418 Input: Values{{IntegerLiteral(1), StringLiteral("Marc"), StringLiteral("Richards")}}, 419 }, 420 `INSERT INTO Singers (SingerId, FirstName, LastName) VALUES (1, "Marc", "Richards")`, 421 reparseDML, 422 }, 423 { 424 &Delete{ 425 Table: "Ta", 426 Where: ComparisonOp{ 427 LHS: ID("C"), 428 Op: Gt, 429 RHS: IntegerLiteral(2), 430 }, 431 }, 432 "DELETE FROM Ta WHERE C > 2", 433 reparseDML, 434 }, 435 { 436 &Update{ 437 Table: "Ta", 438 Items: []UpdateItem{ 439 {Column: "Cb", Value: IntegerLiteral(4)}, 440 {Column: "Ce", Value: StringLiteral("wow")}, 441 {Column: "Cf", Value: ID("Cg")}, 442 {Column: "Cg", Value: Null}, 443 {Column: "Ch", Value: nil}, 444 }, 445 Where: ID("Ca"), 446 }, 447 `UPDATE Ta SET Cb = 4, Ce = "wow", Cf = Cg, Cg = NULL, Ch = DEFAULT WHERE Ca`, 448 reparseDML, 449 }, 450 { 451 Query{ 452 Select: Select{ 453 List: []Expr{ID("A"), ID("B")}, 454 From: []SelectFrom{SelectFromTable{Table: "Table"}}, 455 Where: LogicalOp{ 456 LHS: ComparisonOp{ 457 LHS: ID("C"), 458 Op: Lt, 459 RHS: StringLiteral("whelp"), 460 }, 461 Op: And, 462 RHS: IsOp{ 463 LHS: ID("D"), 464 Neg: true, 465 RHS: Null, 466 }, 467 }, 468 ListAliases: []ID{"", "banana"}, 469 }, 470 Order: []Order{{Expr: ID("OCol"), Desc: true}}, 471 Limit: IntegerLiteral(1000), 472 }, 473 `SELECT A, B AS banana FROM Table WHERE C < "whelp" AND D IS NOT NULL ORDER BY OCol DESC LIMIT 1000`, 474 reparseQuery, 475 }, 476 { 477 Query{ 478 Select: Select{ 479 List: []Expr{ID("A")}, 480 From: []SelectFrom{SelectFromTable{ 481 Table: "Table", 482 Hints: map[string]string{"FORCE_INDEX": "Idx"}, 483 }}, 484 Where: ComparisonOp{ 485 LHS: ID("B"), 486 Op: Eq, 487 RHS: Param("b"), 488 }, 489 }, 490 }, 491 `SELECT A FROM Table@{FORCE_INDEX=Idx} WHERE B = @b`, 492 reparseQuery, 493 }, 494 { 495 Query{ 496 Select: Select{ 497 List: []Expr{ID("A")}, 498 From: []SelectFrom{SelectFromTable{ 499 Table: "Table", 500 Hints: map[string]string{"FORCE_INDEX": "Idx", "GROUPBY_SCAN_OPTIMIZATION": "TRUE"}, 501 }}, 502 Where: ComparisonOp{ 503 LHS: ID("B"), 504 Op: Eq, 505 RHS: Param("b"), 506 }, 507 }, 508 }, 509 `SELECT A FROM Table@{FORCE_INDEX=Idx,GROUPBY_SCAN_OPTIMIZATION=TRUE} WHERE B = @b`, 510 reparseQuery, 511 }, 512 { 513 Query{ 514 Select: Select{ 515 List: []Expr{IntegerLiteral(7)}, 516 }, 517 }, 518 `SELECT 7`, 519 reparseQuery, 520 }, 521 { 522 Query{ 523 Select: Select{ 524 List: []Expr{Func{ 525 Name: "CAST", 526 Args: []Expr{TypedExpr{Expr: IntegerLiteral(7), Type: Type{Base: String}}}, 527 }}, 528 }, 529 }, 530 `SELECT CAST(7 AS STRING)`, 531 reparseQuery, 532 }, 533 { 534 Query{ 535 Select: Select{ 536 List: []Expr{Func{ 537 Name: "SAFE_CAST", 538 Args: []Expr{TypedExpr{Expr: IntegerLiteral(7), Type: Type{Base: Date}}}, 539 }}, 540 }, 541 }, 542 `SELECT SAFE_CAST(7 AS DATE)`, 543 reparseQuery, 544 }, 545 { 546 ComparisonOp{LHS: ID("X"), Op: NotBetween, RHS: ID("Y"), RHS2: ID("Z")}, 547 `X NOT BETWEEN Y AND Z`, 548 reparseExpr, 549 }, 550 { 551 Query{ 552 Select: Select{ 553 List: []Expr{ 554 ID("Desc"), 555 }, 556 }, 557 }, 558 "SELECT `Desc`", 559 reparseQuery, 560 }, 561 { 562 DateLiteral(civil.Date{Year: 2014, Month: time.September, Day: 27}), 563 `DATE '2014-09-27'`, 564 reparseExpr, 565 }, 566 { 567 TimestampLiteral(time.Date(2014, time.September, 27, 12, 34, 56, 123456e3, latz)), 568 `TIMESTAMP '2014-09-27 12:34:56.123456-07:00'`, 569 reparseExpr, 570 }, 571 { 572 JSONLiteral(`{"a": 1}`), 573 `JSON '{"a": 1}'`, 574 reparseExpr, 575 }, 576 { 577 Query{ 578 Select: Select{ 579 List: []Expr{ 580 ID("A"), ID("B"), 581 }, 582 From: []SelectFrom{ 583 SelectFromJoin{ 584 Type: InnerJoin, 585 LHS: SelectFromTable{Table: "Table1"}, 586 RHS: SelectFromTable{Table: "Table2"}, 587 On: ComparisonOp{ 588 LHS: PathExp{"Table1", "A"}, 589 Op: Eq, 590 RHS: PathExp{"Table2", "A"}, 591 }, 592 }, 593 }, 594 }, 595 }, 596 "SELECT A, B FROM Table1 INNER JOIN Table2 ON Table1.A = Table2.A", 597 reparseQuery, 598 }, 599 { 600 Query{ 601 Select: Select{ 602 List: []Expr{ 603 ID("A"), ID("B"), 604 }, 605 From: []SelectFrom{ 606 SelectFromJoin{ 607 Type: InnerJoin, 608 LHS: SelectFromJoin{ 609 Type: InnerJoin, 610 LHS: SelectFromTable{Table: "Table1"}, 611 RHS: SelectFromTable{Table: "Table2"}, 612 On: ComparisonOp{ 613 LHS: PathExp{"Table1", "A"}, 614 Op: Eq, 615 RHS: PathExp{"Table2", "A"}, 616 }, 617 }, 618 RHS: SelectFromTable{Table: "Table3"}, 619 Using: []ID{"X"}, 620 }, 621 }, 622 }, 623 }, 624 "SELECT A, B FROM Table1 INNER JOIN Table2 ON Table1.A = Table2.A INNER JOIN Table3 USING (X)", 625 reparseQuery, 626 }, 627 { 628 Query{ 629 Select: Select{ 630 List: []Expr{ 631 Case{ 632 Expr: ID("X"), 633 WhenClauses: []WhenClause{ 634 {Cond: IntegerLiteral(1), Result: StringLiteral("X")}, 635 {Cond: IntegerLiteral(2), Result: StringLiteral("Y")}, 636 }, 637 ElseResult: Null, 638 }}, 639 }, 640 }, 641 `SELECT CASE X WHEN 1 THEN "X" WHEN 2 THEN "Y" ELSE NULL END`, 642 reparseQuery, 643 }, 644 { 645 Query{ 646 Select: Select{ 647 List: []Expr{ 648 Case{ 649 WhenClauses: []WhenClause{ 650 {Cond: True, Result: StringLiteral("X")}, 651 {Cond: False, Result: StringLiteral("Y")}, 652 }, 653 }}, 654 }, 655 }, 656 `SELECT CASE WHEN TRUE THEN "X" WHEN FALSE THEN "Y" END`, 657 reparseQuery, 658 }, 659 } 660 for _, test := range tests { 661 sql := test.data.SQL() 662 if sql != test.sql { 663 t.Errorf("%v.SQL() wrong.\n got %s\nwant %s", test.data, sql, test.sql) 664 continue 665 } 666 667 // As a confidence check, confirm that parsing the SQL produces the original input. 668 data, err := test.reparse(sql) 669 if err != nil { 670 t.Errorf("Reparsing %q: %v", sql, err) 671 continue 672 } 673 if !reflect.DeepEqual(data, test.data) { 674 t.Errorf("Reparsing %q wrong.\n got %v\nwant %v", sql, data, test.data) 675 } 676 } 677 }