github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/sem/tree/format_test.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package tree_test 12 13 import ( 14 "context" 15 "fmt" 16 "io/ioutil" 17 "path/filepath" 18 "testing" 19 20 "github.com/cockroachdb/cockroach/pkg/internal/rsg" 21 "github.com/cockroachdb/cockroach/pkg/sql/parser" 22 _ "github.com/cockroachdb/cockroach/pkg/sql/sem/builtins" 23 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 24 "github.com/cockroachdb/cockroach/pkg/sql/types" 25 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 26 ) 27 28 func TestFormatStatement(t *testing.T) { 29 defer leaktest.AfterTest(t)() 30 testData := []struct { 31 stmt string 32 f tree.FmtFlags 33 expected string 34 }{ 35 {`CREATE USER foo WITH PASSWORD 'bar'`, tree.FmtSimple, 36 `CREATE USER 'foo' WITH PASSWORD *****`}, 37 {`CREATE USER foo WITH PASSWORD 'bar'`, tree.FmtShowPasswords, 38 `CREATE USER 'foo' WITH PASSWORD 'bar'`}, 39 40 {`CREATE TABLE foo (x INT8)`, tree.FmtAnonymize, 41 `CREATE TABLE _ (_ INT8)`}, 42 {`INSERT INTO foo(x) TABLE bar`, tree.FmtAnonymize, 43 `INSERT INTO _(_) TABLE _`}, 44 {`UPDATE foo SET x = y`, tree.FmtAnonymize, 45 `UPDATE _ SET _ = _`}, 46 {`DELETE FROM foo`, tree.FmtAnonymize, 47 `DELETE FROM _`}, 48 {`TRUNCATE foo`, tree.FmtAnonymize, 49 `TRUNCATE TABLE _`}, 50 {`ALTER TABLE foo RENAME TO bar`, tree.FmtAnonymize, 51 `ALTER TABLE _ RENAME TO _`}, 52 {`SHOW COLUMNS FROM foo`, tree.FmtAnonymize, 53 `SHOW COLUMNS FROM _`}, 54 {`SHOW CREATE TABLE foo`, tree.FmtAnonymize, 55 `SHOW CREATE _`}, 56 {`GRANT SELECT ON bar TO foo`, tree.FmtAnonymize, 57 `GRANT SELECT ON TABLE _ TO _`}, 58 59 {`INSERT INTO a VALUES (-2, +3)`, 60 tree.FmtHideConstants, 61 `INSERT INTO a VALUES (_, _)`}, 62 63 {`INSERT INTO a VALUES (0), (0), (0), (0), (0), (0)`, 64 tree.FmtHideConstants, 65 `INSERT INTO a VALUES (_), (__more5__)`}, 66 {`INSERT INTO a VALUES (0, 0, 0, 0, 0, 0)`, 67 tree.FmtHideConstants, 68 `INSERT INTO a VALUES (_, _, __more4__)`}, 69 {`INSERT INTO a VALUES (ARRAY[0, 0, 0, 0, 0, 0, 0])`, 70 tree.FmtHideConstants, 71 `INSERT INTO a VALUES (ARRAY[_, _, __more5__])`}, 72 {`INSERT INTO a VALUES (ARRAY[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ` + 73 `0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ` + 74 `0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])`, 75 tree.FmtHideConstants, 76 `INSERT INTO a VALUES (ARRAY[_, _, __more30__])`}, 77 78 {`SELECT 1+COALESCE(NULL, 'a', x)-ARRAY[3.14]`, tree.FmtHideConstants, 79 `SELECT (_ + COALESCE(_, _, x)) - ARRAY[_]`}, 80 81 // This here checks encodeSQLString on non-tree.DString strings also 82 // calls encodeSQLString with the right formatter. 83 // See TestFormatExprs below for the test on DStrings. 84 {`CREATE DATABASE foo TEMPLATE = 'bar-baz'`, tree.FmtBareStrings, 85 `CREATE DATABASE foo TEMPLATE = bar-baz`}, 86 {`CREATE DATABASE foo TEMPLATE = 'bar baz'`, tree.FmtBareStrings, 87 `CREATE DATABASE foo TEMPLATE = 'bar baz'`}, 88 {`CREATE DATABASE foo TEMPLATE = 'bar,baz'`, tree.FmtBareStrings, 89 `CREATE DATABASE foo TEMPLATE = 'bar,baz'`}, 90 {`CREATE DATABASE foo TEMPLATE = 'bar{baz}'`, tree.FmtBareStrings, 91 `CREATE DATABASE foo TEMPLATE = 'bar{baz}'`}, 92 93 {`SET "time zone" = UTC`, tree.FmtSimple, 94 `SET "time zone" = utc`}, 95 {`SET "time zone" = UTC`, tree.FmtBareIdentifiers, 96 `SET time zone = utc`}, 97 {`SET "time zone" = UTC`, tree.FmtBareStrings, 98 `SET "time zone" = utc`}, 99 } 100 101 for i, test := range testData { 102 t.Run(fmt.Sprintf("%d %s", i, test.stmt), func(t *testing.T) { 103 stmt, err := parser.ParseOne(test.stmt) 104 if err != nil { 105 t.Fatal(err) 106 } 107 stmtStr := tree.AsStringWithFlags(stmt.AST, test.f) 108 if stmtStr != test.expected { 109 t.Fatalf("expected %q, got %q", test.expected, stmtStr) 110 } 111 }) 112 } 113 } 114 115 func TestFormatTableName(t *testing.T) { 116 defer leaktest.AfterTest(t)() 117 testData := []struct { 118 stmt string 119 expected string 120 }{ 121 {`CREATE TABLE foo (x INT8)`, 122 `CREATE TABLE xoxoxo (x INT8)`}, 123 {`INSERT INTO foo(x) TABLE bar`, 124 `INSERT INTO xoxoxo(x) TABLE xoxoxo`}, 125 {`UPDATE foo SET x = y`, 126 `UPDATE xoxoxo SET x = y`}, 127 {`DELETE FROM foo`, 128 `DELETE FROM xoxoxo`}, 129 {`ALTER TABLE foo RENAME TO bar`, 130 `ALTER TABLE xoxoxo RENAME TO xoxoxo`}, 131 {`SHOW COLUMNS FROM foo`, 132 `SHOW COLUMNS FROM xoxoxo`}, 133 {`SHOW CREATE TABLE foo`, 134 `SHOW CREATE xoxoxo`}, 135 // TODO(knz): TRUNCATE and GRANT table names are removed by 136 // tree.FmtAnonymize but not processed by table formatters. 137 // 138 // {`TRUNCATE foo`, 139 // `TRUNCATE TABLE xoxoxo`}, 140 // {`GRANT SELECT ON bar TO foo`, 141 // `GRANT SELECT ON xoxoxo TO foo`}, 142 } 143 144 f := tree.NewFmtCtx(tree.FmtSimple) 145 f.SetReformatTableNames(func(ctx *tree.FmtCtx, _ *tree.TableName) { 146 ctx.WriteString("xoxoxo") 147 }) 148 149 for i, test := range testData { 150 t.Run(fmt.Sprintf("%d %s", i, test.stmt), func(t *testing.T) { 151 stmt, err := parser.ParseOne(test.stmt) 152 if err != nil { 153 t.Fatal(err) 154 } 155 f.Reset() 156 f.FormatNode(stmt.AST) 157 stmtStr := f.String() 158 if stmtStr != test.expected { 159 t.Fatalf("expected %q, got %q", test.expected, stmtStr) 160 } 161 }) 162 } 163 } 164 165 func TestFormatExpr(t *testing.T) { 166 defer leaktest.AfterTest(t)() 167 testData := []struct { 168 expr string 169 f tree.FmtFlags 170 expected string 171 }{ 172 {`null`, tree.FmtShowTypes, 173 `(NULL)[unknown]`}, 174 {`true`, tree.FmtShowTypes, 175 `(true)[bool]`}, 176 {`123`, tree.FmtShowTypes, 177 `(123)[int]`}, 178 {`123.456`, tree.FmtShowTypes, 179 `(123.456)[decimal]`}, 180 {`'abc'`, tree.FmtShowTypes, 181 `('abc')[string]`}, 182 {`b'abc'`, tree.FmtShowTypes, 183 `('\x616263')[bytes]`}, 184 {`B'10010'`, tree.FmtShowTypes, 185 `(B'10010')[varbit]`}, 186 {`interval '3s'`, tree.FmtShowTypes, 187 `('00:00:03')[interval]`}, 188 {`date '2003-01-01'`, tree.FmtShowTypes, 189 `('2003-01-01')[date]`}, 190 {`timestamp '2003-01-01 00:00:00'`, tree.FmtShowTypes, 191 `('2003-01-01 00:00:00+00:00')[timestamp]`}, 192 {`timestamptz '2003-01-01 00:00:00+03'`, tree.FmtShowTypes, 193 `('2003-01-01 00:00:00+03:00')[timestamptz]`}, 194 {`greatest(unique_rowid(), 12)`, tree.FmtShowTypes, 195 `(greatest((unique_rowid())[int], (12)[int]))[int]`}, 196 197 // While TestFormatStmt above checks StrVals, this here 198 // checks DStrings. 199 {`ARRAY['a','b c','d,e','f{g','h}i']`, tree.FmtBareStrings, 200 `ARRAY[a, 'b c', 'd,e', 'f{g', 'h}i']`}, 201 // TODO(jordan): pg does *not* quote strings merely containing hex 202 // escapes when included in array values. #16487 203 // {`ARRAY[e'j\x10k']`, tree.FmtBareStrings, 204 // `ARRAY[j\x10k]`}, 205 206 {`(-1):::INT`, tree.FmtParsableNumerics, "(-1)"}, 207 {`'NaN':::FLOAT`, tree.FmtParsableNumerics, "'NaN'"}, 208 {`'-Infinity':::FLOAT`, tree.FmtParsableNumerics, "'-Inf'"}, 209 {`'Infinity':::FLOAT`, tree.FmtParsableNumerics, "'+Inf'"}, 210 {`3.00:::FLOAT`, tree.FmtParsableNumerics, "3.0"}, 211 {`(-3.00):::FLOAT`, tree.FmtParsableNumerics, "(-3.0)"}, 212 {`'NaN':::DECIMAL`, tree.FmtParsableNumerics, "'NaN'"}, 213 {`'-Infinity':::DECIMAL`, tree.FmtParsableNumerics, "'-Infinity'"}, 214 {`'Infinity':::DECIMAL`, tree.FmtParsableNumerics, "'Infinity'"}, 215 {`3.00:::DECIMAL`, tree.FmtParsableNumerics, "3.00"}, 216 {`(-3.00):::DECIMAL`, tree.FmtParsableNumerics, "(-3.00)"}, 217 218 {`1`, tree.FmtParsable, "1:::INT8"}, 219 {`1:::INT`, tree.FmtParsable, "1:::INT8"}, 220 {`9223372036854775807`, tree.FmtParsable, "9223372036854775807:::INT8"}, 221 {`9223372036854775808`, tree.FmtParsable, "9223372036854775808:::DECIMAL"}, 222 {`-1`, tree.FmtParsable, "(-1):::INT8"}, 223 {`(-1):::INT`, tree.FmtParsable, "(-1):::INT8"}, 224 {`-9223372036854775808`, tree.FmtParsable, "(-9223372036854775808):::INT8"}, 225 {`-9223372036854775809`, tree.FmtParsable, "(-9223372036854775809):::DECIMAL"}, 226 {`(-92233.1):::FLOAT`, tree.FmtParsable, "(-92233.1):::FLOAT8"}, 227 {`92233.00:::DECIMAL`, tree.FmtParsable, "92233.00:::DECIMAL"}, 228 229 {`B'00100'`, tree.FmtParsable, "B'00100'"}, 230 231 {`unique_rowid() + 123`, tree.FmtParsable, 232 `unique_rowid() + 123:::INT8`}, 233 {`sqrt(123.0) + 456`, tree.FmtParsable, 234 `sqrt(123.0:::DECIMAL) + 456:::DECIMAL`}, 235 {`ROW()`, tree.FmtParsable, `()`}, 236 {`now() + interval '3s'`, tree.FmtSimple, 237 `now() + '00:00:03'`}, 238 {`now() + interval '3s'`, tree.FmtParsable, 239 `now():::TIMESTAMPTZ + '00:00:03':::INTERVAL`}, 240 {`current_date() - date '2003-01-01'`, tree.FmtSimple, 241 `current_date() - '2003-01-01'`}, 242 {`current_date() - date '2003-01-01'`, tree.FmtParsable, 243 `current_date() - '2003-01-01':::DATE`}, 244 {`now() - timestamp '2003-01-01'`, tree.FmtSimple, 245 `now() - '2003-01-01 00:00:00+00:00'`}, 246 {`now() - timestamp '2003-01-01'`, tree.FmtParsable, 247 `now():::TIMESTAMPTZ - '2003-01-01 00:00:00+00:00':::TIMESTAMP`}, 248 {`'+Inf':::DECIMAL + '-Inf':::DECIMAL + 'NaN':::DECIMAL`, tree.FmtParsable, 249 `('Infinity':::DECIMAL + '-Infinity':::DECIMAL) + 'NaN':::DECIMAL`}, 250 {`'+Inf':::FLOAT8 + '-Inf':::FLOAT8 + 'NaN':::FLOAT8`, tree.FmtParsable, 251 `('+Inf':::FLOAT8 + '-Inf':::FLOAT8) + 'NaN':::FLOAT8`}, 252 {`'12:00:00':::TIME`, tree.FmtParsable, 253 `'12:00:00':::TIME`}, 254 {`'63616665-6630-3064-6465-616462656562':::UUID`, tree.FmtParsable, 255 `'63616665-6630-3064-6465-616462656562':::UUID`}, 256 257 {`(123:::INT, 123:::DECIMAL)`, tree.FmtCheckEquivalence, 258 `(123:::INT8, 123:::DECIMAL)`}, 259 260 {`(1, COALESCE(NULL, 123), ARRAY[45.6])`, tree.FmtHideConstants, 261 `(_, COALESCE(_, _), ARRAY[_])`}, 262 } 263 264 ctx := context.Background() 265 for i, test := range testData { 266 t.Run(fmt.Sprintf("%d %s", i, test.expr), func(t *testing.T) { 267 expr, err := parser.ParseExpr(test.expr) 268 if err != nil { 269 t.Fatal(err) 270 } 271 semaContext := tree.MakeSemaContext() 272 typeChecked, err := tree.TypeCheck(ctx, expr, &semaContext, types.Any) 273 if err != nil { 274 t.Fatal(err) 275 } 276 exprStr := tree.AsStringWithFlags(typeChecked, test.f) 277 if exprStr != test.expected { 278 t.Fatalf("expected %q, got %q", test.expected, exprStr) 279 } 280 }) 281 } 282 } 283 284 func TestFormatExpr2(t *testing.T) { 285 defer leaktest.AfterTest(t)() 286 // This tests formatting from an expr AST. Suitable for use if your input 287 // isn't easily creatable from a string without running an Eval. 288 testData := []struct { 289 expr tree.Expr 290 f tree.FmtFlags 291 expected string 292 }{ 293 {tree.NewDOidWithName(tree.DInt(10), types.RegClass, "foo"), 294 tree.FmtParsable, `crdb_internal.create_regclass(10,'foo'):::REGCLASS`}, 295 {tree.NewDOidWithName(tree.DInt(10), types.RegProc, "foo"), 296 tree.FmtParsable, `crdb_internal.create_regproc(10,'foo'):::REGPROC`}, 297 {tree.NewDOidWithName(tree.DInt(10), types.RegType, "foo"), 298 tree.FmtParsable, `crdb_internal.create_regtype(10,'foo'):::REGTYPE`}, 299 {tree.NewDOidWithName(tree.DInt(10), types.RegNamespace, "foo"), 300 tree.FmtParsable, `crdb_internal.create_regnamespace(10,'foo'):::REGNAMESPACE`}, 301 302 // Ensure that nulls get properly type annotated when printed in an 303 // enclosing tuple that has a type for their position within the tuple. 304 {tree.NewDTuple( 305 types.MakeTuple([]*types.T{types.Int, types.String}), 306 tree.DNull, tree.NewDString("foo")), 307 tree.FmtParsable, 308 `(NULL::INT8, 'foo':::STRING)`, 309 }, 310 {tree.NewDTuple( 311 types.MakeTuple([]*types.T{types.Unknown, types.String}), 312 tree.DNull, tree.NewDString("foo")), 313 tree.FmtParsable, 314 `(NULL, 'foo':::STRING)`, 315 }, 316 {&tree.DArray{ 317 ParamTyp: types.Int, 318 Array: tree.Datums{tree.DNull, tree.DNull}, 319 HasNulls: true, 320 }, 321 tree.FmtParsable, 322 `ARRAY[NULL,NULL]:::INT8[]`, 323 }, 324 325 // Ensure that nulls get properly type annotated when printed in an 326 } 327 328 ctx := context.Background() 329 for i, test := range testData { 330 t.Run(fmt.Sprintf("%d %s", i, test.expr), func(t *testing.T) { 331 semaCtx := tree.MakeSemaContext() 332 typeChecked, err := tree.TypeCheck(ctx, test.expr, &semaCtx, types.Any) 333 if err != nil { 334 t.Fatal(err) 335 } 336 exprStr := tree.AsStringWithFlags(typeChecked, test.f) 337 if exprStr != test.expected { 338 t.Fatalf("expected %q, got %q", test.expected, exprStr) 339 } 340 }) 341 } 342 } 343 344 func TestFormatPgwireText(t *testing.T) { 345 defer leaktest.AfterTest(t)() 346 testData := []struct { 347 expr string 348 expected string 349 }{ 350 {`true`, `t`}, 351 {`false`, `f`}, 352 {`ROW(1)`, `(1)`}, 353 {`ROW(1, NULL)`, `(1,)`}, 354 {`ROW(1, true, 3)`, `(1,t,3)`}, 355 {`ROW(1, (2, 3))`, `(1,"(2,3)")`}, 356 {`ROW(1, (2, 'a b'))`, `(1,"(2,""a b"")")`}, 357 {`ROW(1, (2, 'a"b'))`, `(1,"(2,""a""""b"")")`}, 358 {`ROW(1, 2, ARRAY[1,2,3])`, `(1,2,"{1,2,3}")`}, 359 {`ROW(1, 2, ARRAY[1,NULL,3])`, `(1,2,"{1,NULL,3}")`}, 360 {`ROW(1, 2, ARRAY['a','b','c'])`, `(1,2,"{a,b,c}")`}, 361 {`ROW(1, 2, ARRAY[true,false,true])`, `(1,2,"{t,f,t}")`}, 362 {`ARRAY[(1,2),(3,4)]`, `{"(1,2)","(3,4)"}`}, 363 {`ARRAY[(false,'a'),(true,'b')]`, `{"(f,a)","(t,b)"}`}, 364 {`ARRAY[(1,ARRAY[2,NULL])]`, `{"(1,\"{2,NULL}\")"}`}, 365 {`ARRAY[(1,(1,2)),(2,(3,4))]`, `{"(1,\"(1,2)\")","(2,\"(3,4)\")"}`}, 366 367 {`(((1, 'a b', 3), (4, 'c d'), ROW(6)), (7, 8), ROW('e f'))`, 368 `("(""(1,""""a b"""",3)"",""(4,""""c d"""")"",""(6)"")","(7,8)","(""e f"")")`}, 369 370 {`(((1, '2', 3), (4, '5'), ROW(6)), (7, 8), ROW('9'))`, 371 `("(""(1,2,3)"",""(4,5)"",""(6)"")","(7,8)","(9)")`}, 372 373 {`ARRAY[('a b',ARRAY['c d','e f']), ('g h',ARRAY['i j','k l'])]`, 374 `{"(\"a b\",\"{\"\"c d\"\",\"\"e f\"\"}\")","(\"g h\",\"{\"\"i j\"\",\"\"k l\"\"}\")"}`}, 375 376 {`ARRAY[('1',ARRAY['2','3']), ('4',ARRAY['5','6'])]`, 377 `{"(1,\"{2,3}\")","(4,\"{5,6}\")"}`}, 378 379 {`ARRAY[e'\U00002001☃']`, `{ ☃}`}, 380 } 381 ctx := context.Background() 382 var evalCtx tree.EvalContext 383 for i, test := range testData { 384 t.Run(fmt.Sprintf("%d %s", i, test.expr), func(t *testing.T) { 385 expr, err := parser.ParseExpr(test.expr) 386 if err != nil { 387 t.Fatal(err) 388 } 389 semaCtx := tree.MakeSemaContext() 390 typeChecked, err := tree.TypeCheck(ctx, expr, &semaCtx, types.Any) 391 if err != nil { 392 t.Fatal(err) 393 } 394 typeChecked, err = evalCtx.NormalizeExpr(typeChecked) 395 if err != nil { 396 t.Fatal(err) 397 } 398 exprStr := tree.AsStringWithFlags(typeChecked, tree.FmtPgwireText) 399 if exprStr != test.expected { 400 t.Fatalf("expected %s, got %s", test.expected, exprStr) 401 } 402 }) 403 } 404 } 405 406 // BenchmarkFormatRandomStatements measures the time needed to format 407 // 1000 random statements. 408 func BenchmarkFormatRandomStatements(b *testing.B) { 409 // Generate a bunch of random statements. 410 yBytes, err := ioutil.ReadFile(filepath.Join("..", "..", "parser", "sql.y")) 411 if err != nil { 412 b.Fatalf("error reading grammar: %v", err) 413 } 414 // Use a constant seed so multiple runs are consistent. 415 const seed = 1134 416 r, err := rsg.NewRSG(seed, string(yBytes), false) 417 if err != nil { 418 b.Fatalf("error instantiating RSG: %v", err) 419 } 420 strs := make([]string, 1000) 421 stmts := make([]tree.Statement, 1000) 422 for i := 0; i < 1000; { 423 rdm := r.Generate("stmt", 20) 424 stmt, err := parser.ParseOne(rdm) 425 if err != nil { 426 // Some statements (e.g. those containing error terminals) do 427 // not parse. It's all right. Just ignore this and continue 428 // until we have all we want. 429 continue 430 } 431 strs[i] = rdm 432 stmts[i] = stmt.AST 433 i++ 434 } 435 436 // Benchmark the parses. 437 b.Run("parse", func(b *testing.B) { 438 for i := 0; i < b.N; i++ { 439 for _, sql := range strs { 440 _, err := parser.ParseOne(sql) 441 if err != nil { 442 b.Fatal(err) 443 } 444 } 445 } 446 }) 447 448 // Benchmark the formats. 449 b.Run("format", func(b *testing.B) { 450 for i := 0; i < b.N; i++ { 451 for i, stmt := range stmts { 452 f := tree.NewFmtCtx(tree.FmtSimple) 453 f.FormatNode(stmt) 454 strs[i] = f.CloseAndGetString() 455 } 456 } 457 }) 458 }