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 }