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  }