github.com/samiam2013/sqlvet@v0.0.0-20221210043606-d72f678fc0aa/pkg/vet/vet_test.go (about) 1 package vet_test 2 3 import ( 4 "errors" 5 "fmt" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 10 "github.com/samiam2013/sqlvet/pkg/schema" 11 "github.com/samiam2013/sqlvet/pkg/vet" 12 ) 13 14 var mockDbSchema = &schema.Db{ 15 Tables: map[string]schema.Table{ 16 "foo": { 17 Name: "foo", 18 Columns: map[string]schema.Column{ 19 "id": { 20 Name: "id", 21 Type: "int", 22 }, 23 "value": { 24 Name: "value", 25 Type: "varchar", 26 }, 27 }, 28 }, 29 "bar": { 30 Name: "bar", 31 Columns: map[string]schema.Column{ 32 "id": { 33 Name: "id", 34 Type: "int", 35 }, 36 "count": { 37 Name: "count", 38 Type: "int", 39 }, 40 }, 41 }, 42 }, 43 } 44 45 var mockCtx = vet.VetContext{Schema: mockDbSchema} 46 47 // passing 48 func TestInsert(t *testing.T) { 49 testCases := []struct { 50 Name string 51 Query string 52 }{ 53 { 54 "insert", 55 `INSERT INTO foo (id) VALUES (1)`, 56 }, 57 { 58 "insert with select", 59 `INSERT INTO foo (id) 60 SELECT bar.id 61 FROM bar 62 WHERE bar.id = 1`, 63 }, 64 { 65 "insert with select and const", 66 `INSERT INTO foo (id, value) 67 SELECT bar.id, 'test' 68 FROM bar 69 WHERE bar.id = 1`, 70 }, 71 { 72 "insert with subquery", 73 `INSERT INTO foo (id, value) 74 VALUES ( 75 ( 76 SELECT id 77 FROM bar 78 WHERE bar.id = 2 79 ), 80 'test' 81 )`, 82 }, 83 { 84 "insert with return", 85 `INSERT INTO foo (id) VALUES (1) RETURNING value`, 86 }, 87 { 88 "insert with coalesce expr", 89 `INSERT INTO foo ( 90 id, value 91 ) VALUES ( 92 $1, 93 setweight(to_tsvector(substring(coalesce($1, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce($2, '') for 1000000)), 'B') 94 ) RETURNING id`, 95 }, 96 } 97 98 for _, tcase := range testCases { 99 t.Run(tcase.Name, func(t *testing.T) { 100 _, err := vet.ValidateSqlQuery(mockCtx, tcase.Query) 101 if err != nil { 102 vet.DebugQuery(tcase.Query) 103 } 104 assert.NoError(t, err) 105 }) 106 } 107 } 108 109 // passing 110 func TestInvalidInsert(t *testing.T) { 111 testCases := []struct { 112 Name string 113 Query string 114 Err error 115 }{ 116 { 117 "invalid syntax", 118 `INSERT INTO foo (id,) VALUES ($1)`, 119 errors.New("syntax error at or near \")\""), 120 }, 121 { 122 "invalid table", 123 `INSERT INTO foononexist (id) VALUES ($1)`, 124 errors.New("invalid table name: foononexist"), 125 }, 126 { 127 "invalid column", 128 `INSERT INTO foo (id, date, value) VALUES ($1, $2, $3)`, 129 errors.New("column `date` is not defined in table `foo`"), 130 }, 131 { 132 "not enough values", 133 `INSERT INTO foo (id, value) VALUES ($1)`, 134 errors.New("column count 2 doesn't match value count 1"), 135 }, 136 { 137 "too many values", 138 `INSERT INTO foo (id, value) VALUES ($1, $2, $3)`, 139 errors.New("column count 2 doesn't match value count 3"), 140 }, 141 { 142 "invalid column in value list", 143 `INSERT INTO foo (id) VALUES (oops)`, 144 errors.New("column `oops` is not defined in table `foo`"), 145 }, 146 { 147 "invalid column in value list as expression", 148 `INSERT INTO foo (id) VALUES (oops+1)`, 149 errors.New("column `oops` is not defined in table `foo`"), 150 }, 151 { 152 "invalid table from select", 153 `INSERT INTO foo (id, value) 154 SELECT id, count 155 FROM barr 156 WHERE bar.id=2`, 157 errors.New("invalid table name: barr"), 158 }, 159 { 160 "invalid table from select target", 161 `INSERT INTO foo (id, value) 162 SELECT bar.id 163 FROM foo 164 WHERE foo.id=1`, 165 errors.New("table `bar` not available for query"), 166 }, 167 { 168 "invalid column from select", 169 `INSERT INTO foo (id, value) 170 SELECT bar.id, bar.value 171 FROM bar 172 WHERE bar.id=2`, 173 errors.New("column `value` is not defined in table `bar`"), 174 }, 175 { 176 "invalid column from subselect in select", 177 `INSERT INTO foo (id, value) 178 SELECT bar.id, (SELECT 'test' FROM bar WHERE ida = 1) 179 FROM bar 180 WHERE bar.id=2`, 181 fmt.Errorf( 182 "invalid SELECT query in value list: %w", 183 errors.New("column `ida` is not defined in table `bar`")), 184 }, 185 { 186 "invalid table from select join", 187 `INSERT INTO foo (id, value) 188 SELECT bar.id, bar.count 189 FROM bar 190 JOIN barrr b2 ON b2.uid=bar.id 191 WHERE bar.id=2`, 192 errors.New("invalid table name: barrr"), 193 }, 194 { 195 "invalid column from select join", 196 `INSERT INTO foo (id, value) 197 SELECT bar.id, bar.count 198 FROM bar 199 JOIN bar b2 ON b2.uid=bar.id 200 WHERE bar.id=2`, 201 errors.New("column `uid` is not defined in table `b2`"), 202 }, 203 { 204 "invalid column from subquery", 205 `INSERT INTO foo (id, value) 206 VALUES ( 207 ( 208 SELECT ida 209 FROM bar 210 WHERE bar.id = 2 211 ), 212 'test' 213 )`, 214 fmt.Errorf( 215 "invalid value list: %w", 216 errors.New("column `ida` is not defined in table `bar`")), 217 }, 218 { 219 "insert with invalud column return", 220 `INSERT INTO foo (id) VALUES (1) RETURNING uid`, 221 errors.New("column `uid` is not defined in table `foo`"), 222 }, 223 } 224 225 for _, tcase := range testCases { 226 t.Run(tcase.Name, func(t *testing.T) { 227 _, err := vet.ValidateSqlQuery(mockCtx, tcase.Query) 228 if err == nil { 229 vet.DebugQuery(tcase.Query) 230 } 231 assert.Equal(t, tcase.Err, err) 232 }) 233 } 234 } 235 236 // passing 237 func TestInvalidSelect(t *testing.T) { 238 testCases := []struct { 239 Name string 240 Query string 241 Err error 242 }{ 243 { 244 "invalid syntax", 245 `SELECT id, FROM foo`, 246 errors.New("syntax error at or near \"FROM\""), 247 }, 248 { 249 "invalid table", 250 `SELECT id FROM foononexist`, 251 errors.New("invalid table name: foononexist"), 252 }, 253 { 254 "invalid table in target list", 255 `SELECT foononexist.id FROM foo`, 256 errors.New("table `foononexist` not available for query"), 257 }, 258 { 259 "invalid target column", 260 `SELECT id, date, value FROM foo`, 261 errors.New("column `date` is not defined in table `foo`"), 262 }, 263 { 264 "invalid column in where clause", 265 `SELECT id, value FROM foo WHERE date=NOW() AND 'a'='a'`, 266 errors.New("column `date` is not defined in table `foo`"), 267 }, 268 { 269 "invalid column in where null test", 270 `SELECT id, value FROM foo WHERE date IS NULL`, 271 errors.New("column `date` is not defined in table `foo`"), 272 }, 273 { 274 "invalid table in join", 275 `SELECT id, value FROM foo JOIN barr ON foo.id=barr.id`, 276 errors.New("invalid table name: barr"), 277 }, 278 { 279 "invalid column in join", 280 `SELECT bar.id, value FROM foo LEFT JOIN bar ON foo.id=bar.uid WHERE foo.id=1`, 281 errors.New("column `uid` is not defined in table `bar`"), 282 }, 283 { 284 "invalid column in select with multiple joins", 285 `SELECT id 286 FROM foo 287 LEFT JOIN bar b1 ON b1.id = foo.id 288 LEFT JOIN bar b ON b.date = foo.id 289 LEFT JOIN foo f ON f.id = foo.id 290 LEFT JOIN foo f2 ON f2.id = foo.id 291 WHERE value IS NULL`, 292 errors.New("column `date` is not defined in table `b`"), 293 }, 294 { 295 "invalid column in order by", 296 `SELECT id, value FROM foo ORDER BY oops`, 297 errors.New("column `oops` is not defined in table `foo`"), 298 }, 299 { 300 "invalid column in multiple order by", 301 `SELECT id, value FROM foo ORDER BY id, oops`, 302 errors.New("column `oops` is not defined in table `foo`"), 303 }, 304 { 305 "invalid column in group by", 306 `SELECT MAX(id), value FROM foo GROUP BY oops`, 307 errors.New("column `oops` is not defined in table `foo`"), 308 }, 309 { 310 "invalid column in having", 311 `SELECT MAX(id), value FROM foo GROUP BY value HAVING MAX(uid) > 1`, 312 errors.New("column `uid` is not defined in table `foo`"), 313 }, 314 { 315 "invalid column in having with AND", 316 `SELECT MAX(id), value FROM foo GROUP BY value HAVING MAX(oops) > 1 AND MAX(id) < 10`, 317 errors.New("column `oops` is not defined in table `foo`"), 318 }, 319 { 320 "invalid column in distinct", 321 `SELECT DISTINCT oops, value, id FROM foo`, 322 errors.New("column `oops` is not defined in table `foo`"), 323 }, 324 { 325 "invalid column in window", 326 `SELECT ROW_NUMBER() OVER (PARTITION BY oops ORDER BY value) FROM foo`, 327 errors.New("column `oops` is not defined in table `foo`"), 328 }, 329 { 330 "invalid column in window clause", 331 `SELECT wf() OVER w FROM foo WINDOW w AS (PARTITION BY value ORDER BY oops)`, 332 errors.New("column `oops` is not defined in table `foo`"), 333 }, 334 } 335 336 for _, tcase := range testCases { 337 t.Run(tcase.Name, func(t *testing.T) { 338 qparams, err := vet.ValidateSqlQuery(mockCtx, tcase.Query) 339 if err == nil { 340 vet.DebugQuery(tcase.Query) 341 } 342 assert.Equal(t, tcase.Err, err) 343 assert.Equal(t, 0, len(qparams)) 344 }) 345 } 346 } 347 348 // FIXME support ::regclass: 349 // `SELECT COUNT(0) 350 // FROM pg_attribute 351 // WHERE attrelid = $1::regclass 352 // AND attname = $2 353 // AND NOT attisdropped`, 354 355 // passing 356 func TestSelect(t *testing.T) { 357 testCases := []struct { 358 Name string 359 Query string 360 }{ 361 { 362 "ping", 363 `SELECT 1`, 364 }, 365 { 366 "ping with table", 367 `SELECT 1 FROM foo`, 368 }, 369 { 370 "select one column", 371 `SELECT id FROM foo`, 372 }, 373 { 374 "select one column with table prefix", 375 `SELECT foo.id FROM foo`, 376 }, 377 { 378 "select all columns", 379 `SELECT * FROM foo`, 380 }, 381 { 382 "select with where", 383 `SELECT id FROM foo WHERE value='bar' AND id=1`, 384 }, 385 { 386 "select with null test", 387 `SELECT id FROM foo WHERE value IS NULL`, 388 }, 389 { 390 "select with multiple joins", 391 `SELECT id 392 FROM foo 393 LEFT JOIN bar b ON b.id = foo.id 394 LEFT JOIN foo f ON f.id = foo.id 395 WHERE value IS NULL`, 396 }, 397 } 398 399 for _, tcase := range testCases { 400 t.Run(tcase.Name, func(t *testing.T) { 401 qparams, err := vet.ValidateSqlQuery(mockCtx, tcase.Query) 402 if err != nil { 403 vet.DebugQuery(tcase.Query) 404 } 405 assert.NoError(t, err) 406 assert.Equal(t, 0, len(qparams)) 407 }) 408 } 409 } 410 411 // passing 412 func TestUpdate(t *testing.T) { 413 testCases := []struct { 414 Name string 415 Query string 416 }{ 417 { 418 "update all to null", 419 `UPDATE foo SET value=NULL`, 420 }, 421 { 422 "update id for all", 423 `UPDATE foo SET id=1`, 424 }, 425 { 426 "update with where", 427 `UPDATE foo SET value='bar' WHERE id > 1 AND value IS NULL`, 428 }, 429 { 430 "update with boolean in where", 431 `UPDATE foo SET value='bar' WHERE True`, 432 }, 433 { 434 "update with list in where", 435 `UPDATE foo SET value='bar' WHERE id IN (1, 2, 3)`, 436 }, 437 { 438 "update with from", 439 `UPDATE foo SET value=count FROM bar WHERE bar.id=1`, 440 }, 441 { 442 "update with returning", 443 `UPDATE foo SET id=1 RETURNING value`, 444 }, 445 } 446 447 for _, tcase := range testCases { 448 t.Run(tcase.Name, func(t *testing.T) { 449 qparams, err := vet.ValidateSqlQuery(mockCtx, tcase.Query) 450 if err != nil { 451 vet.DebugQuery(tcase.Query) 452 } 453 assert.NoError(t, err) 454 assert.Equal(t, 0, len(qparams)) 455 }) 456 } 457 } 458 459 func TestInvalidUpdate(t *testing.T) { 460 testCases := []struct { 461 Name string 462 Query string 463 Err error 464 }{ 465 { 466 "invalid syntax", 467 `UPDATE foo, SET id=1`, 468 errors.New("syntax error at or near \",\""), 469 }, 470 { 471 "invalid table", 472 `UPDATE foononexist SET id=1`, 473 errors.New("invalid table name: foononexist"), 474 }, 475 { 476 "invalid column", 477 `UPDATE foo SET date=NOW()`, 478 errors.New("column `date` is not defined in table `foo`"), 479 }, 480 { 481 "invalid column in where clause", 482 `UPDATE foo SET value='bar' WHERE date=NOW() OR 1=1`, 483 errors.New("column `date` is not defined in table `foo`"), 484 }, 485 { 486 "invalid table in from clause", 487 `UPDATE foo SET value=count FROM foononexist WHERE foononexist.id=1`, 488 errors.New("invalid table name: foononexist"), 489 }, 490 { 491 "invalid column in from clause", 492 `UPDATE foo SET value=valuecount FROM bar WHERE bar.id=1`, 493 errors.New("column `valuecount` is not defined in any of the table available for query"), 494 }, 495 { 496 "invalid column in returning", 497 `UPDATE foo SET id=1 RETURNING date`, 498 errors.New("column `date` is not defined in table `foo`"), 499 }, 500 } 501 502 for _, tcase := range testCases { 503 t.Run(tcase.Name, func(t *testing.T) { 504 qparams, err := vet.ValidateSqlQuery(mockCtx, tcase.Query) 505 assert.Equal(t, tcase.Err, err) 506 assert.Equal(t, 0, len(qparams)) 507 }) 508 } 509 } 510 511 // passing 512 func TestDelete(t *testing.T) { 513 testCases := []struct { 514 Name string 515 Query string 516 }{ 517 { 518 "delete all", 519 `DELETE FROM foo`, 520 }, 521 { 522 "delete with where", 523 `DELETE FROM foo WHERE id=1 AND value='bar'`, 524 }, 525 { 526 "delete with where and subquery", 527 `DELETE FROM foo WHERE id = (SELECT id FROM foo WHERE id=2 LIMIT 1)`, 528 }, 529 { 530 "delete with returning", 531 `DELETE FROM foo RETURNING id`, 532 }, 533 } 534 535 for _, tcase := range testCases { 536 t.Run(tcase.Name, func(t *testing.T) { 537 qparams, err := vet.ValidateSqlQuery(mockCtx, tcase.Query) 538 if err != nil { 539 vet.DebugQuery(tcase.Query) 540 } 541 assert.NoError(t, err) 542 assert.Equal(t, 0, len(qparams)) 543 }) 544 } 545 } 546 547 func TestInvalidDelete(t *testing.T) { 548 testCases := []struct { 549 Name string 550 Query string 551 Err error 552 }{ 553 { 554 "invalid syntax", 555 `DELETE FROM foo, WHERE id=1`, 556 errors.New("syntax error at or near \",\""), 557 }, 558 { 559 "invalid table", 560 `DELETE FROM foononexist WHERE id=1`, 561 errors.New("invalid table name: foononexist"), 562 }, 563 { 564 "invalid column", 565 `DELETE FROM foo WHERE date=NOW()`, 566 errors.New("column `date` is not defined in table `foo`"), 567 }, 568 { 569 "invalid column", 570 `DELETE FROM foo WHERE bar=NOW() AND id=1`, 571 errors.New("column `bar` is not defined in table `foo`"), 572 }, 573 { 574 "invalid column", 575 `DELETE FROM foo WHERE bar=NOW() OR id=1`, 576 errors.New("column `bar` is not defined in table `foo`"), 577 }, 578 { 579 "invalid column", 580 `DELETE FROM foo WHERE bar=NOW() AND 1=1 OR id=1`, 581 errors.New("column `bar` is not defined in table `foo`"), 582 }, 583 { 584 "invalid column in where subquery", 585 `DELETE FROM foo WHERE id = (SELECT id FROM foo WHERE date=NOW())`, 586 fmt.Errorf( 587 "invalid WHERE clause: %w", 588 errors.New("column `date` is not defined in table `foo`")), 589 }, 590 { 591 "invalid table in where subquery", 592 `DELETE FROM foo WHERE id = (SELECT id FROM foononexist WHERE id=1)`, 593 fmt.Errorf( 594 "invalid WHERE clause: %w", 595 errors.New("invalid table name: foononexist")), 596 }, 597 { 598 "invalid column in return clause", 599 `DELETE FROM foo RETURNING uid`, 600 errors.New("column `uid` is not defined in table `foo`"), 601 }, 602 } 603 604 for _, tcase := range testCases { 605 t.Run(tcase.Name, func(t *testing.T) { 606 qparams, err := vet.ValidateSqlQuery(mockCtx, tcase.Query) 607 assert.Equal(t, tcase.Err, err) 608 assert.Equal(t, 0, len(qparams)) 609 }) 610 } 611 } 612 613 func TestQueryParams(t *testing.T) { 614 testCases := []struct { 615 Name string 616 Query string 617 Params []vet.QueryParam 618 }{ 619 { 620 "select", 621 "SELECT id FROM foo WHERE value=$1", 622 []vet.QueryParam{ 623 {1}, 624 }, 625 }, 626 { 627 "update", 628 "UPDATE foo SET value=$1 WHERE id=$2", 629 []vet.QueryParam{ 630 {1}, 631 {2}, 632 }, 633 }, 634 { 635 "insert", 636 "INSERT INTO foo (id, value) VALUES ($1, $2)", 637 []vet.QueryParam{ 638 {1}, 639 {2}, 640 }, 641 }, 642 { 643 "delete", 644 "DELETE FROM foo WHERE id=$1", 645 []vet.QueryParam{ 646 {1}, 647 }, 648 }, 649 } 650 651 for _, tcase := range testCases { 652 t.Run(tcase.Name, func(t *testing.T) { 653 qparams, err := vet.ValidateSqlQuery(mockCtx, tcase.Query) 654 if err != nil { 655 vet.DebugQuery(tcase.Query) 656 } 657 assert.NoError(t, err) 658 assert.Equal(t, tcase.Params, qparams) 659 }) 660 } 661 }