Skip to content

Commit 9ef4e91

Browse files
author
Dean Karn
authored
Add _string_ & _number_ COERCE types (#14)
1 parent b0848b9 commit 9ef4e91

File tree

4 files changed

+167
-14
lines changed

4 files changed

+167
-14
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
## [0.7.0] - 2023-05-31
10+
### Added
11+
- _string_ & _number_ COERCE types.
12+
913
## [0.6.1] - 2023-05-25
1014
### Fixed
1115
- Fixed AND and OR not early exiting evaluation.
@@ -62,7 +66,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6266
### Added
6367
- Initial conversion from https://github.com/rust-playground/ksql.
6468

65-
[Unreleased]: https://github.com/go-playground/ksql/compare/v0.6.1...HEAD
69+
[Unreleased]: https://github.com/go-playground/ksql/compare/v0.7.0...HEAD
70+
[0.6.1]: https://github.com/go-playground/ksql/compare/v0.7.0...v0.7.0
6671
[0.6.1]: https://github.com/go-playground/ksql/compare/v0.6.0...v0.6.1
6772
[0.6.0]: https://github.com/go-playground/ksql/compare/v0.5.1...v0.6.0
6873
[0.5.1]: https://github.com/go-playground/ksql/compare/v0.5.0...v0.5.1

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
ksql
22
=====
3-
![Project status](https://img.shields.io/badge/version-0.6.1-green.svg)
3+
![Project status](https://img.shields.io/badge/version-0.7.0-green.svg)
44
[![GoDoc](https://godoc.org/github.com/go-playground/ksql?status.svg)](https://pkg.go.dev/github.com/go-playground/ksql)
55
![License](https://img.shields.io/dub/l/vibe-d.svg)
66

@@ -89,12 +89,14 @@ Expressions support most mathematical and string expressions see below for detai
8989

9090
#### COERCE Types
9191

92-
| Type | Description |
93-
|---------------|--------------------------------------------------------------------------------------------------------|
94-
| `_datetime_` | This attempts to convert the type into a DateTime. |
95-
| `_lowercase_` | This converts the text into lowercase. |
96-
| `_uppercase_` | This converts the text into uppercase. |
97-
| `_title_` | This converts the text into title case, when the first letter is capitalized but the rest lower cased. |
92+
| Type | Description |
93+
|---------------|--------------------------------------------------------------------------------------------------------------------------|
94+
| `_datetime_` | This attempts to convert the type into a DateTime. |
95+
| `_lowercase_` | This converts the text into lowercase. |
96+
| `_uppercase_` | This converts the text into uppercase. |
97+
| `_title_` | This converts the text into title case, when the first letter is capitalized but the rest lower cased. |
98+
| `_string_` | This converts the value into a string and supports the Value's String, Number, Bool, DateTime with nanosecond precision. |
99+
| `_number_` | This converts the value into an f64 number and supports the Value's Null, String, Number, Bool and DateTime. |
98100

99101
#### License
100102

parser.go

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,13 @@ func (p *parser) parseValue(token Token) (Expression, error) {
205205
return nil, err
206206
}
207207
expression = coercedConstant{value: value}
208-
} else {
209-
return expression, nil
210208
}
211209
case "_lowercase_":
212210
expression = coerceLowercase{value: expression}
211+
case "_string_":
212+
expression = coerceString{value: expression}
213+
case "_number_":
214+
expression = coerceNumber{value: expression}
213215
case "_uppercase_":
214216
expression = coerceUppercase{value: expression}
215217
case "_title_":
@@ -585,6 +587,72 @@ func (b between) Calculate(src []byte) (any, error) {
585587
}
586588
}
587589

590+
var _ Expression = (*coerceNumber)(nil)
591+
592+
type coerceNumber struct {
593+
value Expression
594+
}
595+
596+
func (c coerceNumber) Calculate(src []byte) (any, error) {
597+
value, err := c.value.Calculate(src)
598+
if err != nil {
599+
return nil, err
600+
}
601+
602+
switch v := value.(type) {
603+
case string:
604+
f, err := strconv.ParseFloat(v, 64)
605+
if err != nil {
606+
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupported type COERCE for value: %v to a number", value)}
607+
}
608+
return f, nil
609+
610+
case float64:
611+
return v, nil
612+
613+
case bool:
614+
if v {
615+
return 1.0, nil
616+
} else {
617+
return 0.0, nil
618+
}
619+
620+
case time.Time:
621+
return float64(v.UnixNano()), nil
622+
623+
default:
624+
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupported type COERCE for value: %v to a number", value)}
625+
}
626+
}
627+
628+
var _ Expression = (*coerceString)(nil)
629+
630+
type coerceString struct {
631+
value Expression
632+
}
633+
634+
func (c coerceString) Calculate(src []byte) (any, error) {
635+
value, err := c.value.Calculate(src)
636+
if err != nil {
637+
return nil, err
638+
}
639+
640+
switch v := value.(type) {
641+
case nil:
642+
return "null", nil
643+
case string:
644+
return v, nil
645+
case float64:
646+
return strconv.FormatFloat(v, 'f', -1, 64), nil
647+
case bool:
648+
return strconv.FormatBool(v), nil
649+
case time.Time:
650+
return v.Format(time.RFC3339Nano), nil
651+
default:
652+
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupported type COERCE for value: %v to a string", value)}
653+
}
654+
}
655+
588656
var _ Expression = (*coerceLowercase)(nil)
589657

590658
type coerceLowercase struct {
@@ -601,7 +669,7 @@ func (c coerceLowercase) Calculate(src []byte) (any, error) {
601669
case string:
602670
return strings.ToLower(v), nil
603671
default:
604-
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupprted type COERCE for value: %v to a lowescase", value)}
672+
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupported type COERCE for value: %v to a lowescase", value)}
605673
}
606674
}
607675

@@ -621,7 +689,7 @@ func (c coerceUppercase) Calculate(src []byte) (any, error) {
621689
case string:
622690
return strings.ToUpper(v), nil
623691
default:
624-
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupprted type COERCE for value: %v to a uppercase", value)}
692+
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupported type COERCE for value: %v to a uppercase", value)}
625693
}
626694
}
627695

@@ -645,7 +713,7 @@ func (c coerceTitle) Calculate(src []byte) (any, error) {
645713
}
646714
return string(unicode.ToUpper(r)) + strings.ToLower(v[1:]), nil
647715
default:
648-
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupprted type COERCE for value: %v to a uppercase", value)}
716+
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupported type COERCE for value: %v to a uppercase", value)}
649717
}
650718
}
651719

@@ -671,7 +739,7 @@ func (c coerceDateTime) Calculate(src []byte) (any, error) {
671739
}
672740
return t, nil
673741
default:
674-
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupprted type COERCE for value: %v to a DateTime", value)}
742+
return nil, ErrUnsupportedCoerce{s: fmt.Sprintf("unsupported type COERCE for value: %v to a DateTime", value)}
675743
}
676744
}
677745

parser_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,84 @@ func TestParser(t *testing.T) {
625625
src: `{}`,
626626
expected: false,
627627
},
628+
{
629+
name: "COERCE string to string",
630+
exp: `COERCE .name _string_`,
631+
src: `{"name":"Joeybloggs"}`,
632+
expected: "Joeybloggs",
633+
},
634+
{
635+
name: "COERCE null to string",
636+
exp: `COERCE .name _string_`,
637+
src: `{"name":null}`,
638+
expected: "null",
639+
},
640+
{
641+
name: "COERCE true bool to string",
642+
exp: `COERCE .name _string_`,
643+
src: `{"name":true}`,
644+
expected: "true",
645+
},
646+
{
647+
name: "COERCE false bool to string",
648+
exp: `COERCE .name _string_`,
649+
src: `{"name":false}`,
650+
expected: "false",
651+
},
652+
{
653+
name: "COERCE number to string",
654+
exp: `COERCE .name _string_`,
655+
src: `{"name":10}`,
656+
expected: "10",
657+
},
658+
{
659+
name: "COERCE number to string 2",
660+
exp: `COERCE .name _string_`,
661+
src: `{"name":10.03}`,
662+
expected: "10.03",
663+
},
664+
{
665+
name: "COERCE DateTime to string",
666+
exp: `COERCE .name _datetime_,_string_`,
667+
src: `{"name":"2023-05-30T06:21:05Z"}`,
668+
expected: "2023-05-30T06:21:05Z",
669+
},
670+
{
671+
name: "COERCE types to concat string",
672+
exp: `.name + ' - Age ' + COERCE .age _string_`,
673+
src: `{"name":"Joeybloggs","age":39}`,
674+
expected: "Joeybloggs - Age 39",
675+
},
676+
{
677+
name: "COERCE Number to Number",
678+
exp: `COERCE .key _number_`,
679+
src: `{"key":1}`,
680+
expected: 1.0,
681+
},
682+
{
683+
name: "COERCE String to Number",
684+
exp: `COERCE .key _number_`,
685+
src: `{"key":"2"}`,
686+
expected: 2.0,
687+
},
688+
{
689+
name: "COERCE true Bool to Number",
690+
exp: `COERCE .key _number_`,
691+
src: `{"key":true}`,
692+
expected: 1.0,
693+
},
694+
{
695+
name: "COERCE false Bool to Number",
696+
exp: `COERCE .key _number_`,
697+
src: `{"key":false}`,
698+
expected: 0.0,
699+
},
700+
{
701+
name: "COERCE DateTime to Number",
702+
exp: `COERCE .key _datetime_,_number_`,
703+
src: `{"key":"2023-05-30T06:21:05Z"}`,
704+
expected: 1.685427665e18,
705+
},
628706
}
629707

630708
for _, tc := range tests {

0 commit comments

Comments
 (0)