github.com/Ali-iotechsys/sqlboiler/v4@v4.0.0-20221208124957-6aec9a5f1f71/queries/query_builders_test.go (about)

     1  package queries
     2  
     3  import (
     4  	"bytes"
     5  	"flag"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/davecgh/go-spew/spew"
    14  	"github.com/volatiletech/sqlboiler/v4/drivers"
    15  )
    16  
    17  var writeGoldenFiles = flag.Bool(
    18  	"test.golden",
    19  	false,
    20  	"Write golden files.",
    21  )
    22  
    23  func newIntPtr(a int) *int {
    24  	return &a
    25  }
    26  
    27  func TestBuildQuery(t *testing.T) {
    28  	t.Parallel()
    29  
    30  	tests := []struct {
    31  		q    *Query
    32  		args []interface{}
    33  	}{
    34  		{&Query{from: []string{"t"}}, nil},
    35  		{&Query{from: []string{"q"}, limit: newIntPtr(5), offset: 6}, nil},
    36  		{&Query{
    37  			from: []string{"q"},
    38  			orderBy: []argClause{
    39  				{"a ASC", []interface{}{}},
    40  				{"b like ? DESC", []interface{}{"stuff"}},
    41  			},
    42  		}, []interface{}{"stuff"}},
    43  		{&Query{from: []string{"t"}, selectCols: []string{"count(*) as ab, thing as bd", `"stuff"`}}, nil},
    44  		{&Query{from: []string{"a", "b"}, selectCols: []string{"count(*) as ab, thing as bd", `"stuff"`}}, nil},
    45  		{&Query{
    46  			selectCols: []string{"a.happy", "r.fun", "q"},
    47  			from:       []string{"happiness as a"},
    48  			joins:      []join{{clause: "rainbows r on a.id = r.happy_id"}},
    49  		}, nil},
    50  		{&Query{
    51  			from:  []string{"happiness as a"},
    52  			joins: []join{{clause: "rainbows r on a.id = r.happy_id"}},
    53  		}, nil},
    54  		{&Query{
    55  			from: []string{"videos"},
    56  			joins: []join{{
    57  				clause: "(select id from users where deleted = ?) u on u.id = videos.user_id",
    58  				args:   []interface{}{true},
    59  			}},
    60  			where: []where{{clause: "videos.deleted = ?", args: []interface{}{false}}},
    61  		}, []interface{}{true, false}},
    62  		{&Query{
    63  			from:    []string{"a"},
    64  			groupBy: []string{"id", "name"},
    65  			where: []where{
    66  				{clause: "a=? or b=?", args: []interface{}{1, 2}},
    67  				{clause: "c=?", args: []interface{}{3}},
    68  			},
    69  			having: []argClause{
    70  				{clause: "id <> ?", args: []interface{}{1}},
    71  				{clause: "length(name, ?) > ?", args: []interface{}{"utf8", 5}},
    72  			},
    73  		}, []interface{}{1, 2, 3, 1, "utf8", 5}},
    74  		{&Query{
    75  			delete: true,
    76  			from:   []string{"thing happy", `upset as "sad"`, "fun", "thing as stuff", `"angry" as mad`},
    77  			where: []where{
    78  				{clause: "a=?", args: []interface{}{1}},
    79  				{clause: "b=?", args: []interface{}{2}},
    80  				{clause: "c=?", args: []interface{}{3}},
    81  			},
    82  		}, []interface{}{1, 2, 3}},
    83  		{&Query{
    84  			delete: true,
    85  			from:   []string{"thing happy", `upset as "sad"`, "fun", "thing as stuff", `"angry" as mad`},
    86  			where: []where{
    87  				{clause: "(id=? and thing=?) or stuff=?", args: []interface{}{1, 2, 3}},
    88  			},
    89  			limit: newIntPtr(5),
    90  		}, []interface{}{1, 2, 3}},
    91  		{&Query{
    92  			from: []string{"thing happy", `"fun"`, `stuff`},
    93  			update: map[string]interface{}{
    94  				"col1":       1,
    95  				`"col2"`:     2,
    96  				`"fun".col3`: 3,
    97  			},
    98  			where: []where{
    99  				{clause: "aa=? or bb=? or cc=?", orSeparator: true, args: []interface{}{4, 5, 6}},
   100  				{clause: "dd=? or ee=? or ff=? and gg=?", args: []interface{}{7, 8, 9, 10}},
   101  			},
   102  			limit: newIntPtr(5),
   103  		}, []interface{}{2, 3, 1, 4, 5, 6, 7, 8, 9, 10}},
   104  		{&Query{from: []string{"cats"}, joins: []join{{JoinInner, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   105  		{&Query{from: []string{"cats c"}, joins: []join{{JoinInner, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   106  		{&Query{from: []string{"cats as c"}, joins: []join{{JoinInner, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   107  		{&Query{from: []string{"cats as c", "dogs as d"}, joins: []join{{JoinInner, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   108  		{&Query{from: []string{"cats"}, joins: []join{{JoinOuterLeft, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   109  		{&Query{from: []string{"cats c"}, joins: []join{{JoinOuterLeft, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   110  		{&Query{from: []string{"cats as c"}, joins: []join{{JoinOuterLeft, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   111  		{&Query{from: []string{"cats as c", "dogs as d"}, joins: []join{{JoinOuterLeft, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   112  		{&Query{from: []string{"cats"}, joins: []join{{JoinOuterRight, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   113  		{&Query{from: []string{"cats c"}, joins: []join{{JoinOuterRight, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   114  		{&Query{from: []string{"cats as c"}, joins: []join{{JoinOuterRight, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   115  		{&Query{from: []string{"cats as c", "dogs as d"}, joins: []join{{JoinOuterRight, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   116  		{&Query{from: []string{"cats"}, joins: []join{{JoinOuterFull, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   117  		{&Query{from: []string{"cats c"}, joins: []join{{JoinOuterFull, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   118  		{&Query{from: []string{"cats as c"}, joins: []join{{JoinOuterFull, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   119  		{&Query{from: []string{"cats as c", "dogs as d"}, joins: []join{{JoinOuterFull, "dogs d on d.cat_id = cats.id", nil}}}, nil},
   120  		{&Query{
   121  			from: []string{"t"},
   122  			withs: []argClause{
   123  				{"cte_0 AS (SELECT * FROM other_t0)", nil},
   124  				{"cte_1 AS (SELECT * FROM other_t1 WHERE thing=? AND stuff=?)", []interface{}{3, 7}},
   125  			},
   126  		}, []interface{}{3, 7},
   127  		},
   128  		{&Query{from: []string{"t"}, distinct: "id"}, nil},
   129  		{&Query{from: []string{"t"}, distinct: "id", count: true}, nil},
   130  		{&Query{from: []string{"t"}, distinct: "id, t.*", joins: []join{{JoinInner, "dogs d on d.cat_id = t.id", nil}}}, nil},
   131  		{&Query{from: []string{"t"}, distinct: "id, t.*", count: true, joins: []join{{JoinInner, "dogs d on d.cat_id = t.id", nil}}}, nil},
   132  		{&Query{from: []string{"t"}, where: []where{{clause: "deleted_at is null"}, {clause: "deleted_at = survives"}}, removeSoftDelete: true}, nil},
   133  	}
   134  
   135  	for i, test := range tests {
   136  		filename := filepath.Join("_fixtures", fmt.Sprintf("%02d.sql", i))
   137  		test.q.dialect = &drivers.Dialect{LQ: '"', RQ: '"', UseIndexPlaceholders: true}
   138  		out, args := BuildQuery(test.q)
   139  
   140  		if *writeGoldenFiles {
   141  			err := os.WriteFile(filename, []byte(out), 0664)
   142  			if err != nil {
   143  				t.Fatalf("Failed to write golden file %s: %s\n", filename, err)
   144  			}
   145  			t.Logf("wrote golden file: %s\n", filename)
   146  			continue
   147  		}
   148  
   149  		byt, err := os.ReadFile(filename)
   150  		if err != nil {
   151  			t.Fatalf("Failed to read golden file %q: %v", filename, err)
   152  		}
   153  
   154  		if string(bytes.TrimSpace(byt)) != out {
   155  			t.Errorf("[%02d] Test failed:\nWant:\n%s\nGot:\n%s", i, byt, out)
   156  		}
   157  
   158  		if !reflect.DeepEqual(args, test.args) {
   159  			t.Errorf("[%02d] Test failed:\nWant:\n%s\nGot:\n%s", i, spew.Sdump(test.args), spew.Sdump(args))
   160  		}
   161  	}
   162  }
   163  
   164  func TestWriteStars(t *testing.T) {
   165  	t.Parallel()
   166  
   167  	tests := []struct {
   168  		In  Query
   169  		Out []string
   170  	}{
   171  		{
   172  			In:  Query{from: []string{`a`}},
   173  			Out: []string{`"a".*`},
   174  		},
   175  		{
   176  			In:  Query{from: []string{`a as b`}},
   177  			Out: []string{`"b".*`},
   178  		},
   179  		{
   180  			In:  Query{from: []string{`a as b`, `c`}},
   181  			Out: []string{`"b".*`, `"c".*`},
   182  		},
   183  		{
   184  			In:  Query{from: []string{`a as b`, `c as d`}},
   185  			Out: []string{`"b".*`, `"d".*`},
   186  		},
   187  	}
   188  
   189  	for i, test := range tests {
   190  		test.In.dialect = &drivers.Dialect{LQ: '"', RQ: '"', UseIndexPlaceholders: true}
   191  		selects := writeStars(&test.In)
   192  		if !reflect.DeepEqual(selects, test.Out) {
   193  			t.Errorf("writeStar test fail %d\nwant: %v\ngot:  %v", i, test.Out, selects)
   194  		}
   195  	}
   196  }
   197  
   198  func TestWhereClause(t *testing.T) {
   199  	t.Parallel()
   200  
   201  	tests := []struct {
   202  		q      Query
   203  		expect string
   204  	}{
   205  		// Or("a=?")
   206  		{
   207  			q: Query{
   208  				where: []where{{clause: "a=?", orSeparator: true}},
   209  			},
   210  			expect: " WHERE (a=$1)",
   211  		},
   212  		// Where("a=?")
   213  		{
   214  			q: Query{
   215  				where: []where{{clause: "a=?"}},
   216  			},
   217  			expect: " WHERE (a=$1)",
   218  		},
   219  		// Where("(a=?)")
   220  		{
   221  			q: Query{
   222  				where: []where{{clause: "(a=?)"}},
   223  			},
   224  			expect: " WHERE ((a=$1))",
   225  		},
   226  		// Where("((a=? OR b=?))")
   227  		{
   228  			q: Query{
   229  				where: []where{{clause: "((a=? OR b=?))"}},
   230  			},
   231  			expect: " WHERE (((a=$1 OR b=$2)))",
   232  		},
   233  		// Where("(a=?)", Or("(b=?)")
   234  		{
   235  			q: Query{
   236  				where: []where{
   237  					{clause: "(a=?)"},
   238  					{clause: "(b=?)", orSeparator: true},
   239  				},
   240  			},
   241  			expect: " WHERE ((a=$1)) OR ((b=$2))",
   242  		},
   243  		// Where("a=? OR b=?")
   244  		{
   245  			q: Query{
   246  				where: []where{{clause: "a=? OR b=?"}},
   247  			},
   248  			expect: " WHERE (a=$1 OR b=$2)",
   249  		},
   250  		// Where("a=?"), Where("b=?")
   251  		{
   252  			q: Query{
   253  				where: []where{{clause: "a=?"}, {clause: "b=?"}},
   254  			},
   255  			expect: " WHERE (a=$1) AND (b=$2)",
   256  		},
   257  		// Where("(a=? AND b=?) OR c=?")
   258  		{
   259  			q: Query{
   260  				where: []where{{clause: "(a=? AND b=?) OR c=?"}},
   261  			},
   262  			expect: " WHERE ((a=$1 AND b=$2) OR c=$3)",
   263  		},
   264  		// Where("a=? OR b=?"), Where("c=? OR d=? OR e=?")
   265  		{
   266  			q: Query{
   267  				where: []where{
   268  					{clause: "(a=? OR b=?)"},
   269  					{clause: "(c=? OR d=? OR e=?)"},
   270  				},
   271  			},
   272  			expect: " WHERE ((a=$1 OR b=$2)) AND ((c=$3 OR d=$4 OR e=$5))",
   273  		},
   274  		// Where("(a=? AND b=?) OR (c=? AND d=? AND e=?) OR f=? OR f=?")
   275  		{
   276  			q: Query{
   277  				where: []where{
   278  					{clause: "(a=? AND b=?) OR (c=? AND d=? AND e=?) OR f=? OR g=?"},
   279  				},
   280  			},
   281  			expect: " WHERE ((a=$1 AND b=$2) OR (c=$3 AND d=$4 AND e=$5) OR f=$6 OR g=$7)",
   282  		},
   283  		// Where("(a=? AND b=?) OR (c=? AND d=? OR e=?) OR f=? OR g=?")
   284  		{
   285  			q: Query{
   286  				where: []where{
   287  					{clause: "(a=? AND b=?) OR (c=? AND d=? OR e=?) OR f=? OR g=?"},
   288  				},
   289  			},
   290  			expect: " WHERE ((a=$1 AND b=$2) OR (c=$3 AND d=$4 OR e=$5) OR f=$6 OR g=$7)",
   291  		},
   292  		// Where("a=? or b=?"), Or("c=? and d=?"), Or("e=? or f=?")
   293  		{
   294  			q: Query{
   295  				where: []where{
   296  					{clause: "a=? or b=?", orSeparator: true},
   297  					{clause: "c=? and d=?", orSeparator: true},
   298  					{clause: "e=? or f=?", orSeparator: true},
   299  				},
   300  			},
   301  			expect: " WHERE (a=$1 or b=$2) OR (c=$3 and d=$4) OR (e=$5 or f=$6)",
   302  		},
   303  		// Where("a=? or b=?"), Or("c=? and d=?"), Or("e=? or f=?")
   304  		{
   305  			q: Query{
   306  				where: []where{
   307  					{clause: "a=? or b=?"},
   308  					{clause: "c=? and d=?", orSeparator: true},
   309  					{clause: "e=? or f=?"},
   310  				},
   311  			},
   312  			expect: " WHERE (a=$1 or b=$2) OR (c=$3 and d=$4) AND (e=$5 or f=$6)",
   313  		},
   314  		// Where("a=?", 1), Or2(Expr(Where("b=? and c=?", 2, 3)))
   315  		{
   316  			q: Query{
   317  				where: []where{
   318  					{clause: "a=?"},
   319  					{kind: whereKindLeftParen, orSeparator: true},
   320  					{clause: "b=? and c=?", orSeparator: true},
   321  					{kind: whereKindRightParen},
   322  				},
   323  			},
   324  			expect: " WHERE a=$1 OR (b=$2 and c=$3)",
   325  		},
   326  	}
   327  
   328  	for i, test := range tests {
   329  		test.q.dialect = &drivers.Dialect{LQ: '"', RQ: '"', UseIndexPlaceholders: true}
   330  		result, _ := whereClause(&test.q, 1)
   331  		if result != test.expect {
   332  			t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, result)
   333  		}
   334  	}
   335  }
   336  
   337  func TestNotInClause(t *testing.T) {
   338  	t.Parallel()
   339  	tests := []struct {
   340  		q      Query
   341  		expect string
   342  		args   []interface{}
   343  	}{
   344  		{
   345  			q: Query{
   346  				where: []where{{kind: whereKindNotIn, clause: "a not in ?", args: []interface{}{}, orSeparator: true}},
   347  			},
   348  			expect: ` WHERE (1=1)`,
   349  		},
   350  		{
   351  			q: Query{
   352  				where: []where{{kind: whereKindNotIn, clause: "a not in ?", args: []interface{}{1}, orSeparator: true}},
   353  			},
   354  			expect: ` WHERE ("a" NOT IN ($1))`,
   355  			args:   []interface{}{1},
   356  		},
   357  	}
   358  	for i, test := range tests {
   359  		test.q.dialect = &drivers.Dialect{LQ: '"', RQ: '"', UseIndexPlaceholders: true}
   360  		result, args := whereClause(&test.q, 1)
   361  		if result != test.expect {
   362  			t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, result)
   363  		}
   364  		if !reflect.DeepEqual(args, test.args) {
   365  			t.Errorf("%d) Mismatch between expected args:\n%#v\n%#v\n", i, test.args, args)
   366  		}
   367  	}
   368  }
   369  
   370  func TestInClause(t *testing.T) {
   371  	t.Parallel()
   372  
   373  	tests := []struct {
   374  		q      Query
   375  		expect string
   376  		args   []interface{}
   377  	}{
   378  		{
   379  			q: Query{
   380  				where: []where{{kind: whereKindIn, clause: "a in ?", args: []interface{}{}, orSeparator: true}},
   381  			},
   382  			expect: ` WHERE (1=0)`,
   383  		},
   384  		{
   385  			q: Query{
   386  				where: []where{
   387  					where{kind: whereKindIn, clause: "a in ?", args: []interface{}{}, orSeparator: true},
   388  					where{kind: whereKindIn, clause: "a in ?", args: []interface{}{1}, orSeparator: true},
   389  				},
   390  			},
   391  			expect: ` WHERE (1=0) OR ("a" IN ($1))`,
   392  			args:   []interface{}{1},
   393  		},
   394  
   395  		{
   396  			q: Query{
   397  				where: []where{{kind: whereKindIn, clause: "a in ?", args: []interface{}{1}, orSeparator: true}},
   398  			},
   399  			expect: ` WHERE ("a" IN ($1))`,
   400  			args:   []interface{}{1},
   401  		},
   402  		{
   403  			q: Query{
   404  				where: []where{{kind: whereKindIn, clause: "a in ?", args: []interface{}{1, 2, 3}}},
   405  			},
   406  			expect: ` WHERE ("a" IN ($1,$2,$3))`,
   407  			args:   []interface{}{1, 2, 3},
   408  		},
   409  		{
   410  			q: Query{
   411  				where: []where{{kind: whereKindIn, clause: "? in ?", args: []interface{}{1, 2, 3}}},
   412  			},
   413  			expect: " WHERE ($1 IN ($2,$3))",
   414  			args:   []interface{}{1, 2, 3},
   415  		},
   416  		{
   417  			q: Query{
   418  				where: []where{{kind: whereKindIn, clause: "( ? , ? ) in ( ? )", orSeparator: true, args: []interface{}{"a", "b", 1, 2, 3, 4}}},
   419  			},
   420  			expect: " WHERE (( $1 , $2 ) IN ( (($3,$4),($5,$6)) ))",
   421  			args:   []interface{}{"a", "b", 1, 2, 3, 4},
   422  		},
   423  		{
   424  			q: Query{
   425  				where: []where{{kind: whereKindIn, clause: `("a")in(?)`, orSeparator: true, args: []interface{}{1, 2, 3}}},
   426  			},
   427  			expect: ` WHERE (("a") IN (($1,$2,$3)))`,
   428  			args:   []interface{}{1, 2, 3},
   429  		},
   430  		{
   431  			q: Query{
   432  				where: []where{{kind: whereKindIn, clause: `("a")in?`, args: []interface{}{1}}},
   433  			},
   434  			expect: ` WHERE (("a") IN ($1))`,
   435  			args:   []interface{}{1},
   436  		},
   437  		{
   438  			q: Query{
   439  				where: []where{
   440  					{clause: "a=?", args: []interface{}{1}},
   441  					{kind: whereKindIn, clause: `?,?,"name" in ?`, orSeparator: true, args: []interface{}{"c", "d", 3, 4, 5, 6, 7, 8}},
   442  					{kind: whereKindIn, clause: `?,?,"name" in ?`, orSeparator: true, args: []interface{}{"e", "f", 9, 10, 11, 12, 13, 14}},
   443  				},
   444  			},
   445  			expect: ` WHERE (a=$1) OR ($2,$3,"name" IN (($4,$5,$6),($7,$8,$9))) OR ($10,$11,"name" IN (($12,$13,$14),($15,$16,$17)))`,
   446  			args:   []interface{}{1, "c", "d", 3, 4, 5, 6, 7, 8, "e", "f", 9, 10, 11, 12, 13, 14},
   447  		},
   448  		{
   449  			q: Query{
   450  				where: []where{
   451  					{kind: whereKindIn, clause: `("a")in`, args: []interface{}{1}},
   452  					{kind: whereKindIn, clause: `("a")in?`, orSeparator: true, args: []interface{}{1}},
   453  				},
   454  			},
   455  			expect: ` WHERE (("a")in) OR (("a") IN ($1))`,
   456  			args:   []interface{}{1, 1},
   457  		},
   458  		{
   459  			q: Query{
   460  				where: []where{
   461  					{kind: whereKindIn, clause: `\?,\? in \?`, args: []interface{}{1}},
   462  					{kind: whereKindIn, clause: `\?,\?in \?`, orSeparator: true, args: []interface{}{1}},
   463  				},
   464  			},
   465  			expect: ` WHERE (?,? IN ?) OR (?,? IN ?)`,
   466  			args:   []interface{}{1, 1},
   467  		},
   468  		{
   469  			q: Query{
   470  				where: []where{
   471  					{kind: whereKindIn, clause: `("a")in`, args: []interface{}{1}},
   472  					{kind: whereKindIn, clause: `("a") in thing`, args: []interface{}{1, 2, 3}},
   473  					{kind: whereKindIn, clause: `("a")in?`, orSeparator: true, args: []interface{}{4, 5, 6}},
   474  				},
   475  			},
   476  			expect: ` WHERE (("a")in) AND (("a") IN thing) OR (("a") IN ($1,$2,$3))`,
   477  			args:   []interface{}{1, 1, 2, 3, 4, 5, 6},
   478  		},
   479  		{
   480  			q: Query{
   481  				where: []where{
   482  					{kind: whereKindIn, clause: `("a")in?`, orSeparator: true, args: []interface{}{4, 5, 6}},
   483  					{kind: whereKindIn, clause: `("a") in thing`, args: []interface{}{1, 2, 3}},
   484  					{kind: whereKindIn, clause: `("a")in`, args: []interface{}{1}},
   485  				},
   486  			},
   487  			expect: ` WHERE (("a") IN ($1,$2,$3)) AND (("a") IN thing) AND (("a")in)`,
   488  			args:   []interface{}{4, 5, 6, 1, 2, 3, 1},
   489  		},
   490  		{
   491  			q: Query{
   492  				where: []where{
   493  					{kind: whereKindIn, clause: `("a")in?`, orSeparator: true, args: []interface{}{4, 5, 6}},
   494  					{kind: whereKindIn, clause: `("a")in`, args: []interface{}{1}},
   495  					{kind: whereKindIn, clause: `("a") in thing`, args: []interface{}{1, 2, 3}},
   496  				},
   497  			},
   498  			expect: ` WHERE (("a") IN ($1,$2,$3)) AND (("a")in) AND (("a") IN thing)`,
   499  			args:   []interface{}{4, 5, 6, 1, 1, 2, 3},
   500  		},
   501  	}
   502  
   503  	for i, test := range tests {
   504  		test.q.dialect = &drivers.Dialect{LQ: '"', RQ: '"', UseIndexPlaceholders: true}
   505  		result, args := whereClause(&test.q, 1)
   506  		if result != test.expect {
   507  			t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, result)
   508  		}
   509  		if !reflect.DeepEqual(args, test.args) {
   510  			t.Errorf("%d) Mismatch between expected args:\n%#v\n%#v\n", i, test.args, args)
   511  		}
   512  	}
   513  }
   514  
   515  func TestLimitClause(t *testing.T) {
   516  	t.Parallel()
   517  
   518  	tests := []struct {
   519  		limit           *int
   520  		expectPredicate func(sql string) bool
   521  	}{
   522  		{nil, func(sql string) bool {
   523  			return !strings.Contains(sql, "LIMIT")
   524  		}},
   525  		{newIntPtr(0), func(sql string) bool {
   526  			return strings.Contains(sql, "LIMIT 0")
   527  		}},
   528  		{newIntPtr(5), func(sql string) bool {
   529  			return strings.Contains(sql, "LIMIT 5")
   530  		}},
   531  	}
   532  
   533  	for i, test := range tests {
   534  		q := &Query{
   535  			limit:   test.limit,
   536  			dialect: &drivers.Dialect{LQ: '"', RQ: '"', UseIndexPlaceholders: true, UseTopClause: false},
   537  		}
   538  		sql, _ := BuildQuery(q)
   539  		if !test.expectPredicate(sql) {
   540  			t.Errorf("%d) Unexpected built SQL query: %s", i, sql)
   541  		}
   542  	}
   543  }
   544  
   545  func TestConvertQuestionMarks(t *testing.T) {
   546  	t.Parallel()
   547  
   548  	tests := []struct {
   549  		clause string
   550  		start  int
   551  		expect string
   552  		count  int
   553  	}{
   554  		{clause: "hello friend", start: 1, expect: "hello friend", count: 0},
   555  		{clause: "thing=?", start: 2, expect: "thing=$2", count: 1},
   556  		{clause: "thing=? and stuff=? and happy=?", start: 2, expect: "thing=$2 and stuff=$3 and happy=$4", count: 3},
   557  		{clause: `thing \? stuff`, start: 2, expect: `thing ? stuff`, count: 0},
   558  		{clause: `thing \? stuff and happy \? fun`, start: 2, expect: `thing ? stuff and happy ? fun`, count: 0},
   559  		{
   560  			clause: `thing \? stuff ? happy \? and mad ? fun \? \? \?`,
   561  			start:  2,
   562  			expect: `thing ? stuff $2 happy ? and mad $3 fun ? ? ?`,
   563  			count:  2,
   564  		},
   565  		{
   566  			clause: `thing ? stuff ? happy \? fun \? ? ?`,
   567  			start:  1,
   568  			expect: `thing $1 stuff $2 happy ? fun ? $3 $4`,
   569  			count:  4,
   570  		},
   571  		{clause: `?`, start: 1, expect: `$1`, count: 1},
   572  		{clause: `???`, start: 1, expect: `$1$2$3`, count: 3},
   573  		{clause: `\?`, start: 1, expect: `?`},
   574  		{clause: `\?\?\?`, start: 1, expect: `???`},
   575  		{clause: `\??\??\??`, start: 1, expect: `?$1?$2?$3`, count: 3},
   576  		{clause: `?\??\??\?`, start: 1, expect: `$1?$2?$3?`, count: 3},
   577  	}
   578  
   579  	for i, test := range tests {
   580  		res, count := convertQuestionMarks(test.clause, test.start)
   581  		if res != test.expect {
   582  			t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, res)
   583  		}
   584  		if count != test.count {
   585  			t.Errorf("%d) Expected count %d, got %d", i, test.count, count)
   586  		}
   587  	}
   588  }
   589  
   590  func TestConvertInQuestionMarks(t *testing.T) {
   591  	t.Parallel()
   592  
   593  	tests := []struct {
   594  		clause string
   595  		start  int
   596  		group  int
   597  		total  int
   598  		expect string
   599  	}{
   600  		{clause: "?", expect: "(($1,$2,$3),($4,$5,$6),($7,$8,$9))", start: 1, total: 9, group: 3},
   601  		{clause: "?", expect: "(($2,$3),($4))", start: 2, total: 3, group: 2},
   602  		{clause: "hello friend", start: 1, expect: "hello friend", total: 0, group: 1},
   603  		{clause: "thing ? thing", start: 2, expect: "thing ($2,$3) thing", total: 2, group: 1},
   604  		{clause: "thing?thing", start: 2, expect: "thing($2)thing", total: 1, group: 1},
   605  		{clause: `thing \? stuff`, start: 2, expect: `thing ? stuff`, total: 0, group: 1},
   606  		{clause: `thing \? stuff and happy \? fun`, start: 2, expect: `thing ? stuff and happy ? fun`, total: 0, group: 1},
   607  		{clause: "thing ? thing ? thing", start: 1, expect: "thing ($1,$2,$3) thing ? thing", total: 3, group: 1},
   608  		{clause: `?`, start: 1, expect: `($1)`, total: 1, group: 1},
   609  		{clause: `???`, start: 1, expect: `($1,$2,$3)??`, total: 3, group: 1},
   610  		{clause: `\?`, start: 1, expect: `?`, total: 0, group: 1},
   611  		{clause: `\?\?\?`, start: 1, expect: `???`, total: 0, group: 1},
   612  		{clause: `\??\??\??`, start: 1, expect: `?($1,$2,$3)????`, total: 3, group: 1},
   613  		{clause: `?\??\??\?`, start: 1, expect: `($1,$2,$3)?????`, total: 3, group: 1},
   614  	}
   615  
   616  	for i, test := range tests {
   617  		res, count := convertInQuestionMarks(true, test.clause, test.start, test.group, test.total)
   618  		if res != test.expect {
   619  			t.Errorf("%d) Mismatch between expect and result:\n%s\n%s\n", i, test.expect, res)
   620  		}
   621  		if count != test.total {
   622  			t.Errorf("%d) Expected %d, got %d", i, test.total, count)
   623  		}
   624  	}
   625  
   626  	res, count := convertInQuestionMarks(false, "?", 1, 3, 9)
   627  	if res != "((?,?,?),(?,?,?),(?,?,?))" {
   628  		t.Errorf("Mismatch between expected and result: %s", res)
   629  	}
   630  	if count != 9 {
   631  		t.Errorf("Expected 9 results, got %d", count)
   632  	}
   633  }
   634  
   635  func TestWriteAsStatements(t *testing.T) {
   636  	t.Parallel()
   637  
   638  	query := Query{
   639  		selectCols: []string{
   640  			`a`,
   641  			`a.fun`,
   642  			`"b"."fun"`,
   643  			`"b".fun`,
   644  			`b."fun"`,
   645  			`a.clown.run`,
   646  			`COUNT(a)`,
   647  		},
   648  		dialect: &drivers.Dialect{LQ: '"', RQ: '"', UseIndexPlaceholders: true},
   649  	}
   650  
   651  	expect := []string{
   652  		`"a"`,
   653  		`"a"."fun" as "a.fun"`,
   654  		`"b"."fun" as "b.fun"`,
   655  		`"b"."fun" as "b.fun"`,
   656  		`"b"."fun" as "b.fun"`,
   657  		`"a"."clown"."run" as "a.clown.run"`,
   658  		`COUNT(a)`,
   659  	}
   660  
   661  	gots := writeAsStatements(&query)
   662  
   663  	for i, got := range gots {
   664  		if expect[i] != got {
   665  			t.Errorf(`%d) want: %s, got: %s`, i, expect[i], got)
   666  		}
   667  	}
   668  }
   669  
   670  func TestWriteComment(t *testing.T) {
   671  	t.Parallel()
   672  
   673  	var buf bytes.Buffer
   674  	query := Query{
   675  		dialect: &drivers.Dialect{LQ: '"', RQ: '"', UseIndexPlaceholders: true},
   676  	}
   677  
   678  	// empty comment
   679  	buf.Reset()
   680  	query.comment = ""
   681  	writeComment(&query, &buf)
   682  	if got := buf.String(); got != "" {
   683  		t.Errorf(`bad empty comment, got: %s`, got)
   684  	}
   685  
   686  	// one line comment
   687  	buf.Reset()
   688  	query.comment = "comment"
   689  	writeComment(&query, &buf)
   690  	if got := buf.String(); got != "-- comment\n" {
   691  		t.Errorf(`bad one line comment, got: %s`, got)
   692  	}
   693  
   694  	// two lines comment
   695  	buf.Reset()
   696  	query.comment = "first\nsecond"
   697  	writeComment(&query, &buf)
   698  	if got := buf.String(); got != "-- first\n-- second\n" {
   699  		t.Errorf(`bad two lines comment, got: %s`, got)
   700  	}
   701  }