github.com/mitranim/sqlb@v0.7.2/t_main_test.go (about)

     1  package sqlb
     2  
     3  import (
     4  	"fmt"
     5  	r "reflect"
     6  	"runtime"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  	u "unsafe"
    11  )
    12  
    13  type Internal struct {
    14  	Id   string `json:"internalId"   db:"id"`
    15  	Name string `json:"internalName" db:"name"`
    16  }
    17  
    18  type External struct {
    19  	Id       string   `json:"externalId"       db:"id"`
    20  	Name     string   `json:"externalName"     db:"name"`
    21  	Internal Internal `json:"externalInternal" db:"internal"`
    22  }
    23  
    24  // nolint:govet
    25  type Embed struct {
    26  	Id        string `json:"embedId"      db:"embed_id"`
    27  	Name      string `json:"embedName"    db:"embed_name"`
    28  	private   string `json:"embedPrivate" db:"embed_private"`
    29  	Untagged0 string ``
    30  	Untagged1 string `db:"-"`
    31  	_         string `db:"blank"`
    32  }
    33  
    34  type Outer struct {
    35  	Embed
    36  	Id       string `json:"outerId"   db:"outer_id"`
    37  	Name     string `json:"outerName" db:"outer_name"`
    38  	OnlyJson string `json:"onlyJson"`
    39  }
    40  
    41  var testOuter = Outer{
    42  	Id:   `outer id`,
    43  	Name: `outer name`,
    44  	Embed: Embed{
    45  		Id:        `embed id`,
    46  		Name:      `embed name`,
    47  		private:   `private`,
    48  		Untagged0: `untagged 0`,
    49  		Untagged1: `untagged 1`,
    50  	},
    51  }
    52  
    53  type Void struct{}
    54  
    55  func (Void) GetVal() any { return `val` }
    56  
    57  type UnitStruct struct {
    58  	One any `db:"one" json:"one"`
    59  }
    60  
    61  func (self UnitStruct) GetOne() any             { return self.One }
    62  func (self UnitStruct) UnaryVoid(any)           {}
    63  func (self UnitStruct) NullaryPair() (_, _ any) { return }
    64  
    65  type UnitStruct1 struct {
    66  	Two any `db:"two" json:"two"`
    67  }
    68  
    69  type PairStruct struct {
    70  	One any `db:"one" json:"one"`
    71  	Two any `db:"two" json:"two"`
    72  }
    73  
    74  func (self PairStruct) GetOne() any { return self.One }
    75  func (self PairStruct) GetTwo() any { return self.Two }
    76  
    77  type PairStruct1 struct {
    78  	Three any `db:"three" json:"three"`
    79  	Four  any `db:"four" json:"four"`
    80  }
    81  
    82  type TrioStruct struct {
    83  	One   any `db:"one" json:"one"`
    84  	Two   any `db:"two" json:"two"`
    85  	Three any `db:"three" json:"three"`
    86  }
    87  
    88  func (self TrioStruct) GetOne() any   { return self.One }
    89  func (self TrioStruct) GetTwo() any   { return self.Two }
    90  func (self TrioStruct) GetThree() any { return self.Three }
    91  
    92  type list = []any
    93  
    94  type Encoder interface {
    95  	fmt.Stringer
    96  	AppenderTo
    97  }
    98  
    99  type EncoderExpr interface {
   100  	Encoder
   101  	Expr
   102  }
   103  
   104  func testEncoder(t testing.TB, exp string, val Encoder) {
   105  	t.Helper()
   106  	eq(t, exp, val.String())
   107  	eq(t, exp, string(val.AppendTo(nil)))
   108  }
   109  
   110  func testEncoderExpr(t testing.TB, exp string, val EncoderExpr) {
   111  	t.Helper()
   112  	testEncoder(t, exp, val)
   113  	eq(t, exp, string(reify(val).Text))
   114  }
   115  
   116  func testExpr(t testing.TB, exp R, val EncoderExpr) {
   117  	t.Helper()
   118  	testEncoderExpr(t, string(exp.Text), val)
   119  	testExprs(t, exp, val)
   120  }
   121  
   122  func testExprs(t testing.TB, exp R, vals ...Expr) {
   123  	t.Helper()
   124  	eq(t, exp, reify(vals...))
   125  }
   126  
   127  func exprTest(t testing.TB) func(R, EncoderExpr) {
   128  	return func(exp R, val EncoderExpr) {
   129  		t.Helper()
   130  		testExpr(t, exp, val)
   131  	}
   132  }
   133  
   134  func reify(vals ...Expr) R {
   135  	text, args := Reify(vals...)
   136  	return R{text, args}.Norm()
   137  }
   138  
   139  // Short for "reified".
   140  func rei(text string, args ...any) R { return R{text, args}.Norm() }
   141  
   142  func reiFrom(text []byte, args []any) R {
   143  	return R{bytesToMutableString(text), args}.Norm()
   144  }
   145  
   146  func reifyParamExpr(expr ParamExpr, dict ArgDict) R {
   147  	return reiFrom(expr.AppendParamExpr(nil, nil, dict))
   148  }
   149  
   150  func reifyUnparamPreps(vals ...string) (text []byte, args []any) {
   151  	for _, val := range vals {
   152  		text, args = Preparse(val).AppendParamExpr(text, args, nil)
   153  	}
   154  	return
   155  }
   156  
   157  /*
   158  Short for "reified". Test-only for now. Similar to `StrQ` but uses ordinal params.
   159  Implements `Expr` incorrectly, see below.
   160  */
   161  type R struct {
   162  	Text string
   163  	Args list
   164  }
   165  
   166  /*
   167  Note: this is NOT a valid implementation of an expr with ordinal params. When
   168  the input args are non-empty, a real implementation would have to parse its own
   169  text to renumerate the params, appending that modified text to the output.
   170  */
   171  func (self R) AppendExpr(text []byte, args list) ([]byte, list) {
   172  	text = append(text, self.Text...)
   173  	args = append(args, self.Args...)
   174  	return text, args
   175  }
   176  
   177  /*
   178  Without this equivalence, tests break due to slice prealloc/growth in
   179  `StrQ.AppendExpr`, violating some equality tests. We don't really care about the
   180  difference between nil and zero-length arg lists.
   181  */
   182  func (self R) Norm() R {
   183  	if self.Args == nil {
   184  		self.Args = list{}
   185  	}
   186  	return self
   187  }
   188  
   189  func eq(t testing.TB, exp, act any) {
   190  	t.Helper()
   191  	if !r.DeepEqual(exp, act) {
   192  		t.Fatalf(`
   193  expected (detailed):
   194  	%#[1]v
   195  actual (detailed):
   196  	%#[2]v
   197  expected (simple):
   198  	%[1]v
   199  actual (simple):
   200  	%[2]v
   201  `, exp, act)
   202  	}
   203  }
   204  
   205  func sliceIs[A any](t testing.TB, exp, act []A) {
   206  	t.Helper()
   207  
   208  	expSlice := *(*sliceHeader)(u.Pointer(&exp))
   209  	actSlice := *(*sliceHeader)(u.Pointer(&act))
   210  
   211  	if !r.DeepEqual(expSlice, actSlice) {
   212  		t.Fatalf(`
   213  expected (slice):
   214  	%#[1]v
   215  actual (slice):
   216  	%#[2]v
   217  expected (detailed):
   218  	%#[3]v
   219  actual (detailed):
   220  	%#[4]v
   221  expected (simple):
   222  	%[3]v
   223  actual (simple):
   224  	%[4]v
   225  `, expSlice, actSlice, exp, act)
   226  	}
   227  }
   228  
   229  // nolint:structcheck
   230  type sliceHeader struct {
   231  	dat u.Pointer
   232  	len int
   233  	cap int
   234  }
   235  
   236  func notEq(t testing.TB, exp, act any) {
   237  	t.Helper()
   238  	if r.DeepEqual(exp, act) {
   239  		t.Fatalf(`
   240  unexpected equality (detailed):
   241  	%#[1]v
   242  unexpected equality (simple):
   243  	%[1]v
   244  	`, exp, act)
   245  	}
   246  }
   247  
   248  func panics(t testing.TB, msg string, fun func()) {
   249  	t.Helper()
   250  	val := catchAny(fun)
   251  
   252  	if val == nil {
   253  		t.Fatalf(`expected %v to panic, found no panic`, funcName(fun))
   254  	}
   255  
   256  	str := fmt.Sprint(val)
   257  	if !strings.Contains(str, msg) {
   258  		t.Fatalf(`
   259  expected %v to panic with a message containing:
   260  	%v
   261  found the following message:
   262  	%v
   263  `, funcName(fun), msg, str)
   264  	}
   265  }
   266  
   267  func funcName(val any) string {
   268  	return runtime.FuncForPC(r.ValueOf(val).Pointer()).Name()
   269  }
   270  
   271  func catchAny(fun func()) (val any) {
   272  	defer recAny(&val)
   273  	fun()
   274  	return
   275  }
   276  
   277  func recAny(ptr *any) { *ptr = recover() }
   278  
   279  var hugeBui = MakeBui(len(hugeQuery)*2, len(hugeQueryArgs)*2)
   280  
   281  func tHugeBuiEmpty(t testing.TB) {
   282  	eq(t, Bui{[]byte{}, list{}}, hugeBui)
   283  }
   284  
   285  func parseTime(str string) *time.Time {
   286  	inst, err := time.Parse(time.RFC3339, str)
   287  	try(err)
   288  	return &inst
   289  }
   290  
   291  const hugeQuery = /*pgsql*/ `
   292  	select col_name
   293  	from
   294  		table_name
   295  
   296  		left join table_name using (col_name)
   297  
   298  		inner join (
   299  			select agg(col_name) as col_name
   300  			from table_name
   301  			where (
   302  				false
   303  				or col_name = 'enum_value'
   304  				or (:arg_one and (:arg_two or col_name = :arg_three))
   305  			)
   306  			group by col_name
   307  		) as table_name using (col_name)
   308  
   309  		left join (
   310  			select
   311  				table_name.col_name
   312  			from
   313  				table_name
   314  				left join table_name on table_name.col_name = table_name.col_name
   315  			where
   316  				false
   317  				or :arg_four::type_name is null
   318  				or table_name.col_name between :arg_four and (:arg_four + 'literal input'::some_type)
   319  		) as table_name using (col_name)
   320  
   321  		left join (
   322  			select distinct col_name as col_name
   323  			from table_name
   324  			where (:arg_five::type_name[] is null or col_name = any(:arg_five))
   325  		) as table_name using (col_name)
   326  
   327  		left join (
   328  			select distinct col_name as col_name
   329  			from table_name
   330  			where (:arg_six::type_name[] is null or col_name = any(:arg_six))
   331  		) as table_name using (col_name)
   332  	where
   333  		true
   334  		and (:arg_seven or col_name in (table table_name))
   335  		and (:arg_four :: type_name   is null or table_name.col_name is not null)
   336  		and (:arg_five :: type_name[] is null or table_name.col_name is not null)
   337  		and (:arg_six  :: type_name[] is null or table_name.col_name is not null)
   338  		and (
   339  			false
   340  			or not col_name
   341  			or (:arg_eight and (:arg_two or col_name = :arg_three))
   342  		)
   343  		and (
   344  			false
   345  			or not col_name
   346  			or (:arg_nine and (:arg_two or col_name = :arg_three))
   347  		)
   348  		and (:arg_ten or not col_name)
   349  		and (:arg_eleven   :: type_name is null or col_name            @@ func_name(:arg_eleven))
   350  		and (:arg_fifteen  :: type_name is null or col_name            <> :arg_fifteen)
   351  		and (:arg_sixteen  :: type_name is null or col_name            =  :arg_sixteen)
   352  		and (:arg_twelve   :: type_name is null or col_name            =  :arg_twelve)
   353  		and (:arg_thirteen :: type_name is null or func_name(col_name) <= :arg_thirteen)
   354  	:arg_fourteen
   355  `
   356  
   357  var hugeQueryArgs = Dict{
   358  	`arg_one`:      nil,
   359  	`arg_two`:      nil,
   360  	`arg_three`:    nil,
   361  	`arg_four`:     nil,
   362  	`arg_five`:     nil,
   363  	`arg_six`:      nil,
   364  	`arg_seven`:    nil,
   365  	`arg_eight`:    nil,
   366  	`arg_nine`:     nil,
   367  	`arg_ten`:      nil,
   368  	`arg_eleven`:   nil,
   369  	`arg_twelve`:   nil,
   370  	`arg_thirteen`: nil,
   371  	`arg_fourteen`: nil,
   372  	`arg_fifteen`:  nil,
   373  	`arg_sixteen`:  nil,
   374  }
   375  
   376  type HaserTrue struct{}
   377  
   378  func (HaserTrue) Has(string) bool { return true }
   379  
   380  type HaserFalse struct{}
   381  
   382  func (HaserFalse) Has(string) bool { return false }
   383  
   384  type HaserSlice []string
   385  
   386  func (self HaserSlice) Has(tar string) bool {
   387  	for _, val := range self {
   388  		if val == tar {
   389  			return true
   390  		}
   391  	}
   392  	return false
   393  }
   394  
   395  type Stringer [1]any
   396  
   397  func (self Stringer) String() string {
   398  	if self[0] == nil {
   399  		return ``
   400  	}
   401  	return fmt.Sprint(self[0])
   402  }
   403  
   404  func (self Stringer) AppendTo(buf []byte) []byte {
   405  	return append(buf, self.String()...)
   406  }