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  }