github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/s3select/sql/value_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package sql
    19  
    20  import (
    21  	"fmt"
    22  	"math"
    23  	"strconv"
    24  	"testing"
    25  	"time"
    26  )
    27  
    28  // valueBuilders contains one constructor for each value type.
    29  // Values should match if type is the same.
    30  var valueBuilders = []func() *Value{
    31  	FromNull,
    32  	func() *Value {
    33  		return FromBool(true)
    34  	},
    35  	func() *Value {
    36  		return FromBytes([]byte("byte contents"))
    37  	},
    38  	func() *Value {
    39  		return FromFloat(math.Pi)
    40  	},
    41  	func() *Value {
    42  		return FromInt(0x1337)
    43  	},
    44  	func() *Value {
    45  		t, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
    46  		if err != nil {
    47  			panic(err)
    48  		}
    49  		return FromTimestamp(t)
    50  	},
    51  	func() *Value {
    52  		return FromString("string contents")
    53  	},
    54  }
    55  
    56  // altValueBuilders contains one constructor for each value type.
    57  // Values are zero values and should NOT match the values in valueBuilders, except Null type.
    58  var altValueBuilders = []func() *Value{
    59  	FromNull,
    60  	func() *Value {
    61  		return FromBool(false)
    62  	},
    63  	func() *Value {
    64  		return FromBytes(nil)
    65  	},
    66  	func() *Value {
    67  		return FromFloat(0)
    68  	},
    69  	func() *Value {
    70  		return FromInt(0)
    71  	},
    72  	func() *Value {
    73  		return FromTimestamp(time.Time{})
    74  	},
    75  	func() *Value {
    76  		return FromString("")
    77  	},
    78  }
    79  
    80  func TestValue_SameTypeAs(t *testing.T) {
    81  	type fields struct {
    82  		a, b Value
    83  	}
    84  	type test struct {
    85  		name   string
    86  		fields fields
    87  		wantOk bool
    88  	}
    89  	var tests []test
    90  	for i := range valueBuilders {
    91  		a := valueBuilders[i]()
    92  		for j := range valueBuilders {
    93  			b := valueBuilders[j]()
    94  			tests = append(tests, test{
    95  				name: fmt.Sprint(a.GetTypeString(), "==", b.GetTypeString()),
    96  				fields: fields{
    97  					a: *a, b: *b,
    98  				},
    99  				wantOk: i == j,
   100  			})
   101  		}
   102  	}
   103  
   104  	for _, tt := range tests {
   105  		t.Run(tt.name, func(t *testing.T) {
   106  			if gotOk := tt.fields.a.SameTypeAs(tt.fields.b); gotOk != tt.wantOk {
   107  				t.Errorf("SameTypeAs() = %v, want %v", gotOk, tt.wantOk)
   108  			}
   109  		})
   110  	}
   111  }
   112  
   113  func TestValue_Equals(t *testing.T) {
   114  	type fields struct {
   115  		a, b Value
   116  	}
   117  	type test struct {
   118  		name   string
   119  		fields fields
   120  		wantOk bool
   121  	}
   122  	var tests []test
   123  	for i := range valueBuilders {
   124  		a := valueBuilders[i]()
   125  		for j := range valueBuilders {
   126  			b := valueBuilders[j]()
   127  			tests = append(tests, test{
   128  				name: fmt.Sprint(a.GetTypeString(), "==", b.GetTypeString()),
   129  				fields: fields{
   130  					a: *a, b: *b,
   131  				},
   132  				wantOk: i == j,
   133  			})
   134  		}
   135  	}
   136  	for i := range valueBuilders {
   137  		a := valueBuilders[i]()
   138  		for j := range altValueBuilders {
   139  			b := altValueBuilders[j]()
   140  			tests = append(tests, test{
   141  				name: fmt.Sprint(a.GetTypeString(), "!=", b.GetTypeString()),
   142  				fields: fields{
   143  					a: *a, b: *b,
   144  				},
   145  				// Only Null == Null
   146  				wantOk: a.IsNull() && b.IsNull() && i == 0 && j == 0,
   147  			})
   148  		}
   149  	}
   150  	for _, tt := range tests {
   151  		t.Run(tt.name, func(t *testing.T) {
   152  			if gotOk := tt.fields.a.Equals(tt.fields.b); gotOk != tt.wantOk {
   153  				t.Errorf("Equals() = %v, want %v", gotOk, tt.wantOk)
   154  			}
   155  		})
   156  	}
   157  }
   158  
   159  func TestValue_CSVString(t *testing.T) {
   160  	type test struct {
   161  		name    string
   162  		want    string
   163  		wantAlt string
   164  	}
   165  
   166  	tests := []test{
   167  		{
   168  			name:    valueBuilders[0]().String(),
   169  			want:    "",
   170  			wantAlt: "",
   171  		},
   172  		{
   173  			name:    valueBuilders[1]().String(),
   174  			want:    "true",
   175  			wantAlt: "false",
   176  		},
   177  		{
   178  			name:    valueBuilders[2]().String(),
   179  			want:    "byte contents",
   180  			wantAlt: "",
   181  		},
   182  		{
   183  			name:    valueBuilders[3]().String(),
   184  			want:    "3.141592653589793",
   185  			wantAlt: "0",
   186  		},
   187  		{
   188  			name:    valueBuilders[4]().String(),
   189  			want:    "4919",
   190  			wantAlt: "0",
   191  		},
   192  		{
   193  			name:    valueBuilders[5]().String(),
   194  			want:    "2006-01-02T15:04:05Z",
   195  			wantAlt: "0001T",
   196  		},
   197  		{
   198  			name:    valueBuilders[6]().String(),
   199  			want:    "string contents",
   200  			wantAlt: "",
   201  		},
   202  	}
   203  
   204  	for i, tt := range tests {
   205  		t.Run(tt.name, func(t *testing.T) {
   206  			v := valueBuilders[i]()
   207  			vAlt := altValueBuilders[i]()
   208  			if got := v.CSVString(); got != tt.want {
   209  				t.Errorf("CSVString() = %v, want %v", got, tt.want)
   210  			}
   211  			if got := vAlt.CSVString(); got != tt.wantAlt {
   212  				t.Errorf("CSVString() = %v, want %v", got, tt.wantAlt)
   213  			}
   214  		})
   215  	}
   216  }
   217  
   218  func TestValue_bytesToInt(t *testing.T) {
   219  	type fields struct {
   220  		value interface{}
   221  	}
   222  	tests := []struct {
   223  		name   string
   224  		fields fields
   225  		want   int64
   226  		wantOK bool
   227  	}{
   228  		{
   229  			name: "zero",
   230  			fields: fields{
   231  				value: []byte("0"),
   232  			},
   233  			want:   0,
   234  			wantOK: true,
   235  		},
   236  		{
   237  			name: "minuszero",
   238  			fields: fields{
   239  				value: []byte("-0"),
   240  			},
   241  			want:   0,
   242  			wantOK: true,
   243  		},
   244  		{
   245  			name: "one",
   246  			fields: fields{
   247  				value: []byte("1"),
   248  			},
   249  			want:   1,
   250  			wantOK: true,
   251  		},
   252  		{
   253  			name: "minusone",
   254  			fields: fields{
   255  				value: []byte("-1"),
   256  			},
   257  			want:   -1,
   258  			wantOK: true,
   259  		},
   260  		{
   261  			name: "plusone",
   262  			fields: fields{
   263  				value: []byte("+1"),
   264  			},
   265  			want:   1,
   266  			wantOK: true,
   267  		},
   268  		{
   269  			name: "max",
   270  			fields: fields{
   271  				value: []byte(strconv.FormatInt(math.MaxInt64, 10)),
   272  			},
   273  			want:   math.MaxInt64,
   274  			wantOK: true,
   275  		},
   276  		{
   277  			name: "min",
   278  			fields: fields{
   279  				value: []byte(strconv.FormatInt(math.MinInt64, 10)),
   280  			},
   281  			want:   math.MinInt64,
   282  			wantOK: true,
   283  		},
   284  		{
   285  			name: "max-overflow",
   286  			fields: fields{
   287  				value: []byte("9223372036854775808"),
   288  			},
   289  			// Seems to be what strconv.ParseInt returns
   290  			want:   math.MaxInt64,
   291  			wantOK: false,
   292  		},
   293  		{
   294  			name: "min-underflow",
   295  			fields: fields{
   296  				value: []byte("-9223372036854775809"),
   297  			},
   298  			// Seems to be what strconv.ParseInt returns
   299  			want:   math.MinInt64,
   300  			wantOK: false,
   301  		},
   302  		{
   303  			name: "zerospace",
   304  			fields: fields{
   305  				value: []byte(" 0"),
   306  			},
   307  			want:   0,
   308  			wantOK: true,
   309  		},
   310  		{
   311  			name: "onespace",
   312  			fields: fields{
   313  				value: []byte("1 "),
   314  			},
   315  			want:   1,
   316  			wantOK: true,
   317  		},
   318  		{
   319  			name: "minusonespace",
   320  			fields: fields{
   321  				value: []byte(" -1 "),
   322  			},
   323  			want:   -1,
   324  			wantOK: true,
   325  		},
   326  		{
   327  			name: "plusonespace",
   328  			fields: fields{
   329  				value: []byte("\t+1\t"),
   330  			},
   331  			want:   1,
   332  			wantOK: true,
   333  		},
   334  		{
   335  			name: "scientific",
   336  			fields: fields{
   337  				value: []byte("3e5"),
   338  			},
   339  			want:   0,
   340  			wantOK: false,
   341  		},
   342  		{
   343  			// No support for prefixes
   344  			name: "hex",
   345  			fields: fields{
   346  				value: []byte("0xff"),
   347  			},
   348  			want:   0,
   349  			wantOK: false,
   350  		},
   351  	}
   352  	for _, tt := range tests {
   353  		t.Run(tt.name, func(t *testing.T) {
   354  			v := &Value{
   355  				value: tt.fields.value,
   356  			}
   357  			got, got1 := v.bytesToInt()
   358  			if got != tt.want {
   359  				t.Errorf("bytesToInt() got = %v, want %v", got, tt.want)
   360  			}
   361  			if got1 != tt.wantOK {
   362  				t.Errorf("bytesToInt() got1 = %v, want %v", got1, tt.wantOK)
   363  			}
   364  		})
   365  	}
   366  }
   367  
   368  func TestValue_bytesToFloat(t *testing.T) {
   369  	type fields struct {
   370  		value interface{}
   371  	}
   372  	tests := []struct {
   373  		name   string
   374  		fields fields
   375  		want   float64
   376  		wantOK bool
   377  	}{
   378  		// Copied from TestValue_bytesToInt.
   379  		{
   380  			name: "zero",
   381  			fields: fields{
   382  				value: []byte("0"),
   383  			},
   384  			want:   0,
   385  			wantOK: true,
   386  		},
   387  		{
   388  			name: "minuszero",
   389  			fields: fields{
   390  				value: []byte("-0"),
   391  			},
   392  			want:   0,
   393  			wantOK: true,
   394  		},
   395  		{
   396  			name: "one",
   397  			fields: fields{
   398  				value: []byte("1"),
   399  			},
   400  			want:   1,
   401  			wantOK: true,
   402  		},
   403  		{
   404  			name: "minusone",
   405  			fields: fields{
   406  				value: []byte("-1"),
   407  			},
   408  			want:   -1,
   409  			wantOK: true,
   410  		},
   411  		{
   412  			name: "plusone",
   413  			fields: fields{
   414  				value: []byte("+1"),
   415  			},
   416  			want:   1,
   417  			wantOK: true,
   418  		},
   419  		{
   420  			name: "maxint",
   421  			fields: fields{
   422  				value: []byte(strconv.FormatInt(math.MaxInt64, 10)),
   423  			},
   424  			want:   math.MaxInt64,
   425  			wantOK: true,
   426  		},
   427  		{
   428  			name: "minint",
   429  			fields: fields{
   430  				value: []byte(strconv.FormatInt(math.MinInt64, 10)),
   431  			},
   432  			want:   math.MinInt64,
   433  			wantOK: true,
   434  		},
   435  		{
   436  			name: "max-overflow-int",
   437  			fields: fields{
   438  				value: []byte("9223372036854775808"),
   439  			},
   440  			// Seems to be what strconv.ParseInt returns
   441  			want:   math.MaxInt64,
   442  			wantOK: true,
   443  		},
   444  		{
   445  			name: "min-underflow-int",
   446  			fields: fields{
   447  				value: []byte("-9223372036854775809"),
   448  			},
   449  			// Seems to be what strconv.ParseInt returns
   450  			want:   math.MinInt64,
   451  			wantOK: true,
   452  		},
   453  		{
   454  			name: "max",
   455  			fields: fields{
   456  				value: []byte(strconv.FormatFloat(math.MaxFloat64, 'g', -1, 64)),
   457  			},
   458  			want:   math.MaxFloat64,
   459  			wantOK: true,
   460  		},
   461  		{
   462  			name: "min",
   463  			fields: fields{
   464  				value: []byte(strconv.FormatFloat(-math.MaxFloat64, 'g', -1, 64)),
   465  			},
   466  			want:   -math.MaxFloat64,
   467  			wantOK: true,
   468  		},
   469  		{
   470  			name: "max-overflow",
   471  			fields: fields{
   472  				value: []byte("1.797693134862315708145274237317043567981e+309"),
   473  			},
   474  			// Seems to be what strconv.ParseInt returns
   475  			want:   math.Inf(1),
   476  			wantOK: false,
   477  		},
   478  		{
   479  			name: "min-underflow",
   480  			fields: fields{
   481  				value: []byte("-1.797693134862315708145274237317043567981e+309"),
   482  			},
   483  			// Seems to be what strconv.ParseInt returns
   484  			want:   math.Inf(-1),
   485  			wantOK: false,
   486  		},
   487  		{
   488  			name: "smallest-pos",
   489  			fields: fields{
   490  				value: []byte(strconv.FormatFloat(math.SmallestNonzeroFloat64, 'g', -1, 64)),
   491  			},
   492  			want:   math.SmallestNonzeroFloat64,
   493  			wantOK: true,
   494  		},
   495  		{
   496  			name: "smallest-pos",
   497  			fields: fields{
   498  				value: []byte(strconv.FormatFloat(-math.SmallestNonzeroFloat64, 'g', -1, 64)),
   499  			},
   500  			want:   -math.SmallestNonzeroFloat64,
   501  			wantOK: true,
   502  		},
   503  		{
   504  			name: "zerospace",
   505  			fields: fields{
   506  				value: []byte(" 0"),
   507  			},
   508  			want:   0,
   509  			wantOK: true,
   510  		},
   511  		{
   512  			name: "onespace",
   513  			fields: fields{
   514  				value: []byte("1 "),
   515  			},
   516  			want:   1,
   517  			wantOK: true,
   518  		},
   519  		{
   520  			name: "minusonespace",
   521  			fields: fields{
   522  				value: []byte(" -1 "),
   523  			},
   524  			want:   -1,
   525  			wantOK: true,
   526  		},
   527  		{
   528  			name: "plusonespace",
   529  			fields: fields{
   530  				value: []byte("\t+1\t"),
   531  			},
   532  			want:   1,
   533  			wantOK: true,
   534  		},
   535  		{
   536  			name: "scientific",
   537  			fields: fields{
   538  				value: []byte("3e5"),
   539  			},
   540  			want:   300000,
   541  			wantOK: true,
   542  		},
   543  		{
   544  			// No support for prefixes
   545  			name: "hex",
   546  			fields: fields{
   547  				value: []byte("0xff"),
   548  			},
   549  			want:   0,
   550  			wantOK: false,
   551  		},
   552  	}
   553  	for _, tt := range tests {
   554  		t.Run(tt.name, func(t *testing.T) {
   555  			v := Value{
   556  				value: tt.fields.value,
   557  			}
   558  			got, got1 := v.bytesToFloat()
   559  			diff := math.Abs(got - tt.want)
   560  			if diff > floatCmpTolerance {
   561  				t.Errorf("bytesToFloat() got = %v, want %v", got, tt.want)
   562  			}
   563  			if got1 != tt.wantOK {
   564  				t.Errorf("bytesToFloat() got1 = %v, want %v", got1, tt.wantOK)
   565  			}
   566  		})
   567  	}
   568  }
   569  
   570  func TestValue_bytesToBool(t *testing.T) {
   571  	type fields struct {
   572  		value interface{}
   573  	}
   574  	tests := []struct {
   575  		name    string
   576  		fields  fields
   577  		wantVal bool
   578  		wantOk  bool
   579  	}{
   580  		{
   581  			name: "true",
   582  			fields: fields{
   583  				value: []byte("true"),
   584  			},
   585  			wantVal: true,
   586  			wantOk:  true,
   587  		},
   588  		{
   589  			name: "false",
   590  			fields: fields{
   591  				value: []byte("false"),
   592  			},
   593  			wantVal: false,
   594  			wantOk:  true,
   595  		},
   596  		{
   597  			name: "t",
   598  			fields: fields{
   599  				value: []byte("t"),
   600  			},
   601  			wantVal: true,
   602  			wantOk:  true,
   603  		},
   604  		{
   605  			name: "f",
   606  			fields: fields{
   607  				value: []byte("f"),
   608  			},
   609  			wantVal: false,
   610  			wantOk:  true,
   611  		},
   612  		{
   613  			name: "1",
   614  			fields: fields{
   615  				value: []byte("1"),
   616  			},
   617  			wantVal: true,
   618  			wantOk:  true,
   619  		},
   620  		{
   621  			name: "0",
   622  			fields: fields{
   623  				value: []byte("0"),
   624  			},
   625  			wantVal: false,
   626  			wantOk:  true,
   627  		},
   628  		{
   629  			name: "truespace",
   630  			fields: fields{
   631  				value: []byte(" true "),
   632  			},
   633  			wantVal: true,
   634  			wantOk:  true,
   635  		},
   636  		{
   637  			name: "truetabs",
   638  			fields: fields{
   639  				value: []byte("\ttrue\t"),
   640  			},
   641  			wantVal: true,
   642  			wantOk:  true,
   643  		},
   644  		{
   645  			name: "TRUE",
   646  			fields: fields{
   647  				value: []byte("TRUE"),
   648  			},
   649  			wantVal: true,
   650  			wantOk:  true,
   651  		},
   652  		{
   653  			name: "FALSE",
   654  			fields: fields{
   655  				value: []byte("FALSE"),
   656  			},
   657  			wantVal: false,
   658  			wantOk:  true,
   659  		},
   660  		{
   661  			name: "invalid",
   662  			fields: fields{
   663  				value: []byte("no"),
   664  			},
   665  			wantVal: false,
   666  			wantOk:  false,
   667  		},
   668  	}
   669  	for _, tt := range tests {
   670  		t.Run(tt.name, func(t *testing.T) {
   671  			v := Value{
   672  				value: tt.fields.value,
   673  			}
   674  			gotVal, gotOk := v.bytesToBool()
   675  			if gotVal != tt.wantVal {
   676  				t.Errorf("bytesToBool() gotVal = %v, want %v", gotVal, tt.wantVal)
   677  			}
   678  			if gotOk != tt.wantOk {
   679  				t.Errorf("bytesToBool() gotOk = %v, want %v", gotOk, tt.wantOk)
   680  			}
   681  		})
   682  	}
   683  }